STM32CubeMX serie 08 - Comunicación SPI (W25Q64, módulo inalámbrico NRF24L01)

====>>> Resumen del artículo (con código resumen) <<<====

1. Preparativos

1.1 Hardware utilizado

Experimento de lectura y escritura de EEPROM (W25Q64): Mini placa de desarrollo de átomo puntual, maestro STM32F103RCT6

Experimento de comunicación: agregue un chino común, el control principal STM32F103ZET6.

1.2 Introducción al SPI

Interfaz de periféricos en serie SPI (interfaz periférica en serie )

  1. Desarrollado por Motorola
  2. Bus de comunicación síncrono, dúplex completo y de alta velocidad
  3. se requieren cuatro cables
  4. Reloj hasta 18Mhz

La interfaz SPI generalmente usa 4 cables para la comunicación:

  • MISO Entrada de datos maestros, salida de datos esclavos
  • Salida de datos maestros MOSI, entrada de datos esclavos
  • Señal de reloj SCLK, generada por el maestro
  • Señal de selección de chip del dispositivo esclavo CS, controlada por el dispositivo maestro

SPI también puede tener una situación de uno a muchos y seleccionar a qué esclavo enviar o recibir datos de acuerdo con la señal de selección del chip CS.
inserte la descripción de la imagen aquí

1.3 Generar proyecto

1.3.1 Crear maestro de selección de proyectos

inserte la descripción de la imagen aquí

1.3.2 Configuración del sistema

Configure la fuente del reloj
inserte la descripción de la imagen aquí
Configure el modo de depuración (puede verificarlo si necesita descargar y depurar ST-Link)
inserte la descripción de la imagen aquí
Configurar el árbol del reloj (puede ingresar directamente 72 en HCLK y luego presionar Enter para configurarlo automáticamente)
inserte la descripción de la imagen aquí

1.3.3 Directorio del proyecto de configuración

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

2. Experimento de lectura y escritura de EEPROM (W25Q64)

EEPROM (memoria de solo lectura programable y borrable eléctricamente) se refiere a la memoria de solo lectura programable y borrable eléctricamente. Es un chip de memoria que no pierde datos después de un corte de energía.

2.1 Introducción a W25Q64

Descripción esquemática
inserte la descripción de la imagen aquí
del pin del chip:

  1. Pin de selección de chip CS. Nivel bajo significa seleccionado.
  2. Interfaz de salida de datos DO SPI
  3. Pin de protección contra escritura de hardware WP, el nivel alto de entrada puede escribir datos normalmente, el nivel bajo de entrada prohíbe la escritura.
  4. Tierra pública GND
  5. Interfaz de entrada de datos DI SPI
  6. Interfaz de reloj CLK SPI
  7. Interfaz de almacenamiento de estado HOLD, nivel bajo de entrada para prohibir el funcionamiento del chip, nivel alto de entrada para operar el chip normalmente.
  8. Interfaz de alimentación VCC, fuente de alimentación 2,7-3,6

La descripción de almacenamiento es
W25Q64, donde 64 significa que la capacidad de almacenamiento del chip es de 64 Mbit, es decir 8M 字节(B).

  1. Todo el chip de 8M bytes se divide en 128 bloques, cada bloque es de 64kb;
  2. Cada bloque de 64k bytes se divide en 16 sectores, cada sector es de 4K bytes (4096 bytes);
  3. Cada sector de 4K bytes se divide en 16 páginas de 256 bytes cada una.

2.2 Implementación del código

PA2 es una señal de selección de chip , simplemente configúrelo como salida push-pull.
inserte la descripción de la imagen aquí
configuración SPI
inserte la descripción de la imagen aquí

El factor de división de frecuencia es 4, porque el reloj SPI puede alcanzar hasta 18 Mhz, y el reloj aquí es de 72 Mhz, que es exactamente 18 Mhz después de la división de frecuencia por cuatro.

Configure la redirección del puerto serie para una fácil observación –>Configuración de la redirección del puerto serie<–

Esta parte del código es mucho, así que después de generar el proyecto.

  1. Cree un archivo separado en la carpeta del proyecto icodepara almacenar nuestro propio código.
  2. En la carpeta icode, cree otra W25Q64文件夹para almacenar códigos relacionados con W25Q64.
  3. w25qxx.cCree dos archivos en la carpeta W25Q64 w25qxx.h.
    inserte la descripción de la imagen aquí

Agregar rutas de archivo de origen y encabezado

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

Escribe el código de la siguiente manera:
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.cprueba en función principal

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

Verificación de efectos
Compilar y grabar
Asistente de puerto serie Link
inserte la descripción de la imagen aquí

3. Comunicación del módulo inalámbrico NRF24L01

3.1 Introducción al Módulo

Introducción

  1. es un módulo de comunicación inalámbrica;
  2. Trabaja en la banda de frecuencia libre y abierta de 2,4 GHz;
  3. La tasa de comunicación puede alcanzar hasta 2 Mbps;
  4. La comunicación entre la MCU y el módulo utiliza la interfaz SPI.

El módulo de la derecha en la siguiente figura es. De hecho, hay muchos módulos similares, y los principios y códigos de funcionamiento son básicamente los mismos. Por ejemplo, el módulo de la izquierda está fabricado en China. El código de prueba se puede utilizar universalmente .
Por favor agregue una descripción de la imagen
Por ejemplo, yo tengo todos estos domésticos, hace unos años cuando salieron por primera vez en su tienda eran más baratos, así que me compré un par de todos, ¡Jajajajajajaja! ! !
inserte la descripción de la imagen aquí

De hecho, mi código también fue cambiado de las rutinas en su sitio web oficial. Su código fue escrito por mí mismo y estaba escrito en la biblioteca estándar, pero ahora uso CubeMX y uso la biblioteca HAL para escribir el código, así que cambié Úsalo. Si hay rutinas que requieren otros modelos de control maestro, puedes ir y echar un vistazo.
URL del sitio web oficial (la empresa recuerda liquidar la tarifa de publicidad, ¡gracias!): http://www.gisemi.com/

Interfaz del módulo (ocho pines en total):

  1. CSN: la línea de selección de chip del chip, el chip funciona a bajo nivel;
  2. SCK: línea de reloj controlada por chip (reloj SPI);
  3. MISO: línea de datos de control de chip (MISO de SPI);
  4. MOSI: línea de datos de control de chip (MOSI de SPI);
  5. IRQ: señal de interrupción, el chip NRF24L01 recibe datos o envía datos, etc., generará una interrupción de borde descendente;
  6. CE: la línea de control de modo del chip determina el estado de funcionamiento del chip.

Conexión de la placa de desarrollo MCU:

La comunicación requiere dos placas de desarrollo y dos módulos.

  • Placa de desarrollo 1: Mini placa de desarrollo atom puntual, el control principal es STM32F103RCT6.
  • Placa de desarrollo 2: placa de desarrollo Puzhong-Zhunrui-Z100, el control principal es STM32F103ZET6.

3.2 Configuración SPI

Debido a que aquí uso dos controles maestros diferentes, necesito crear dos proyectos, que son los mismos que en el primer capítulo, simplemente elija un control maestro diferente.
Si está utilizando dos del mismo maestro, elija el mismo maestro.

La configuración específica de SPI no necesita distinguir si se usa para enviar o recibir, solo necesita configurar el SPI usado y los pines de habilitación relacionados de acuerdo con el diagrama esquemático.

3.2.1 Configuración SPI1

La placa de desarrollo Mini atom puntual utiliza SPI1 para la comunicación, y el diagrama esquemático se muestra en la figura.
inserte la descripción de la imagen aquí
Aquí SPI1 se utiliza para la comunicación.
inserte la descripción de la imagen aquí

NRF24L01 requiere una frecuencia de reloj que no supere los 8Mhz.

Para los tres pines restantes, IRQ corresponde a una entrada pull-up, y la línea de control y la selección de chip son salidas push-pull.
inserte la descripción de la imagen aquí
Al mismo tiempo, configure la redirección del puerto serie para recibir, lo cual es conveniente para la observación.
===>>>Configuración del puerto serie<<<===

3.2.2 Configuración SPI2

La placa de desarrollo Puzhong-Zhunrui-Z100 utiliza SPI2 para la comunicación, y el diagrama esquemático se muestra en la figura.
inserte la descripción de la imagen aquí
Aquí SPI2 se utiliza para la comunicación.
inserte la descripción de la imagen aquí
Para los tres pines restantes, IRQ corresponde a una entrada pull-up, y la línea de control y la selección de chip son salidas push-pull.
inserte la descripción de la imagen aquí
Al mismo tiempo, configure la redirección del puerto serie para recibir, lo cual es conveniente para la observación.
===>>>Configuración del puerto serie<<<===

Tenga en cuenta que si los dos usan dos SPI diferentes, por ejemplo, SPI1 y SPI2 se usan aquí, porque los relojes de los dos SPI son diferentes, por lo que los coeficientes de división de frecuencia no pueden ser los mismos, y la frecuencia y otra información después de la configuración deben ser garantizado que es lo mismo.

SPI1 de STM32 está en APB2, SPI2 y SPI3 están en APB1, la frecuencia más alta de APB1 es 36 MHz y la frecuencia más alta de APB2 es 72 MHz.

3.3 Implementación del código

3.3.1 Añadir código de conductor

Después de generar el proyecto, agregue el código de controlador de NRF24L01 al proyecto (ambos proyectos son obligatorios).
===>>>Código del conductor (no se requieren puntos, descargar directamente)<<<===

icodeCree una carpeta en el directorio del proyecto , cree NRF24L01una carpeta en ella y agréguele nrf24L01.harchivos nrf24l01.c(ambos proyectos son obligatorios).
inserte la descripción de la imagen aquí
Agregue el código y la ruta del archivo de encabezado en Keil (se han escrito muchos artículos anteriores, por lo que no mostraré la imagen aquí).

3.3.2 Modificación del controlador

De hecho, no hay muchas modificaciones. Tanto el emisor como el receptor son lo mismo.

  1. En nrf24L01.hel archivo, modifique qué SPI está utilizando.
  2. En nrf24L01.hel archivo, modifique la definición de los pines CS, CE e IRQ. Si usa la misma etiqueta que yo en la configuración gráfica, no necesita modificarla.
  3. En nrf24L01.cel archivo, modifique qué SPI se usa (solo un lugar en el diagrama).
    inserte la descripción de la imagen aquí
    inserte la descripción de la imagen aquí

3.4 Función principal

remitente

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

Extremo de recepción

/* 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 Pruebas

Primero pruebe el extremo de envío.
La placa de desarrollo está conectada al asistente de depuración del puerto serie.
Restablezca la placa de desarrollo.

  1. Si el módulo no está enchufado al principio, siempre verificará si el módulo existe y le indicará que no existe 0;
  2. Después de encontrar la detección, se emite un aviso;
  3. Posteriormente, el módulo sigue enviando datos y da un aviso de envío exitoso;
  4. Tanto si se recibe como si no, siempre se enviará.

inserte la descripción de la imagen aquí
Luego pruebe el extremo receptor.
Apague primero el extremo emisor y solo encienda el extremo receptor. Puede ver que los pasos de inicialización son los mismos que los anteriores. Pero una vez completada la inicialización, esperará a recibir datos.
inserte la descripción de la imagen aquí
Luego abra el remitente, puede recibir los datos enviados por el remitente.
inserte la descripción de la imagen aquí
Desconecte el módulo transmisor y los datos no se podrán recibir de nuevo.

Vuelva a poner el código del controlador, ¡bienvenido a que le guste! ! ! ===>>>Código del conductor (no se requieren puntos, descargar directamente)<<<===

Después de trabajar en este módulo durante un día y medio, finalmente terminé de escribir este capítulo del blog.

Supongo que te gusta

Origin blog.csdn.net/weixin_46253745/article/details/127851093
Recomendado
Clasificación