STM32CubeMX série 08 - Communication SPI (module sans fil W25Q64, NRF24L01)

====>>> Résumé de l'article (avec résumé du code) <<<====

1. Préparatifs

1.1. Matériel utilisé

Expérience de lecture et d'écriture EEPROM (W25Q64): Mini carte de développement d'atome ponctuel, maître STM32F103RCT6

Expérience de communication : ajoutez un chinois commun, le contrôle principal STM32F103ZET6.

1.2. Introduction à SPI

Interface périphérique série SPI (Serial Peripheral interface )

  1. Développé par Motorola
  2. Bus de communication synchrone, duplex intégral et haut débit
  3. quatre fils sont nécessaires
  4. Horloge jusqu'à 18Mhz

L'interface SPI utilise généralement 4 fils pour la communication :

  • Entrée de données maître MISO, sortie de données esclave
  • Sortie de données maître MOSI, entrée de données esclave
  • Signal d'horloge SCLK, généré par le maître
  • Signal de sélection de puce de l'appareil CS Slave, contrôlé par l'appareil maître

SPI peut également avoir une situation un à plusieurs et sélectionner à quel esclave envoyer ou recevoir des données en fonction du signal de sélection de puce CS.
insérez la description de l'image ici

1.3. Générer un projet

1.3.1. Créer un modèle de sélection de projets

insérez la description de l'image ici

1.3.2 Configuration du système

Configurer la source d'horloge
insérez la description de l'image ici
Configurer le mode de débogage (vous pouvez le vérifier si vous avez besoin de télécharger et de déboguer ST-Link)
insérez la description de l'image ici
Configurer l'arbre d'horloge (vous pouvez entrer directement 72 dans HCLK, puis appuyer sur Entrée pour configurer automatiquement)
insérez la description de l'image ici

1.3.3. Répertoire du projet de configuration

insérez la description de l'image ici
insérez la description de l'image ici

2. Expérience de lecture et d'écriture EEPROM (W25Q64)

EEPROM (mémoire morte programmable effaçable électriquement) fait référence à une mémoire morte programmable effaçable électriquement. C'est une puce mémoire qui ne perd pas de données après une panne de courant.

2.1. Présentation de W25Q64

Description schématique de la broche à puce :
insérez la description de l'image ici

  1. Broche de sélection de puce CS. Niveau bas signifie sélectionné.
  2. Interface de sortie de données DO SPI
  3. Broche de protection en écriture matérielle WP, l'entrée de haut niveau peut écrire des données normalement, l'entrée de bas niveau interdit l'écriture.
  4. Terre publique GND
  5. Interface d'entrée de données DI SPI
  6. Interface d'horloge CLK SPI
  7. Interface de stockage d'état HOLD, entrée de niveau bas pour interdire le fonctionnement de la puce, entrée de niveau haut pour faire fonctionner la puce normalement.
  8. Interface d'alimentation VCC, alimentation 2.7-3.6

La description de stockage est
W25Q64, où 64 signifie que la capacité de stockage de la puce est de 64M bits, c'est-à-dire 8M 字节(B).

  1. L'ensemble des octets de la puce 8M est divisé en 128 blocs, chaque bloc est de 64kb;
  2. Chaque bloc de 64k octets est divisé en 16 secteurs, chaque secteur est de 4K octets (4096 octets);
  3. Chaque secteur de 4K octets est divisé en 16 pages de 256 octets chacune.

2.2. Mise en œuvre du code

PA2 est un signal de sélection de puce , il suffit de le définir comme sortie push-pull.
insérez la description de l'image ici
Paramétrage SPI
insérez la description de l'image ici

Le facteur de division de fréquence est de 4, car l'horloge SPI peut atteindre jusqu'à 18 Mhz, et l'horloge ici est de 72 Mhz, soit exactement 18 Mhz après division de fréquence par quatre.

Configurez la redirection du port série pour une observation facile ->Configuration de la redirection du port série<–

Cette partie du code est beaucoup, donc après avoir généré le projet.

  1. Créez un fichier séparé dans le dossier du projet icodepour stocker notre propre code.
  2. Sous le dossier icode, créez-en un autre W25Q64文件夹pour stocker les codes liés à W25Q64.
  3. w25qxx.cCréez w25qxx.hdeux fichiers dans le dossier W25Q64 .
    insérez la description de l'image ici

Ajouter des chemins de fichier source et d'en-tête

insérez la description de l'image ici
insérez la description de l'image ici

Écrivez le code comme suit :
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 en fonction principale

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

Vérification des effets
Compilation et gravure
Assistant de port série Link
insérez la description de l'image ici

3. Communication du module sans fil NRF24L01

3.1. Présentation des modules

Introduction

  1. est un module de communication sans fil ;
  2. Travaillez dans la bande de fréquence libre et ouverte de 2,4 GHz ;
  3. Le taux de communication peut atteindre jusqu'à 2 Mbps ;
  4. La communication entre le MCU et le module utilise l'interface SPI.

Le module à droite dans la figure ci-dessous est. En fait, il existe de nombreux modules similaires, et les principes de fonctionnement et les codes sont fondamentalement les mêmes.Par exemple, le module de gauche est fabriqué en Chine. Le code de test peut être utilisé universellement .
Veuillez ajouter une description de l'image
Par exemple, j'ai tous ces modèles domestiques. Il y a quelques années, quand ils sont sortis pour la première fois dans son magasin, ils étaient moins chers, alors j'en ai acheté une paire, HaHaHaHaHaHaHa ! ! !
insérez la description de l'image ici

En fait, mon code a également été modifié par rapport aux routines sur son site officiel. Son code a été écrit par moi-même, et il a été écrit dans la bibliothèque standard, mais maintenant j'utilise CubeMX et j'utilise la bibliothèque HAL pour écrire le code, donc j'ai changé Utilisez-le. S'il y a des routines qui nécessitent d'autres modèles de contrôle principal, vous pouvez y jeter un coup d'œil.
URL du site officiel (l'entreprise n'oublie pas de régler les frais de publicité, merci !) : http://www.gisemi.com/

Interface du module (huit broches au total) :

  1. CSN : la ligne de sélection de puce de la puce, la puce fonctionne à bas niveau ;
  2. SCK : ligne d'horloge contrôlée par puce (horloge SPI) ;
  3. MISO : ligne de données de contrôle de puce (MISO de SPI) ;
  4. MOSI : ligne de données de contrôle de puce (MOSI de SPI) ;
  5. IRQ : signal d'interruption, la puce NRF24L01 reçoit des données, ou envoie des données, etc., générera une interruption de front descendant ;
  6. CE : La ligne de contrôle de mode de la puce détermine l'état de fonctionnement de la puce.

Connexion de la carte de développement MCU :

La communication nécessite deux cartes de développement et deux modules.

  • Carte de développement 1: carte de développement atom Mini ponctuelle, le contrôle principal est STM32F103RCT6.
  • Carte de développement 2 : carte de développement Puzhong-Zhunrui-Z100, le contrôle principal est STM32F103ZET6.

3.2 Configuration SPI

Parce que j'utilise ici deux contrôles maîtres différents, je dois créer deux projets, qui sont identiques au premier chapitre, il suffit de choisir un contrôle maître différent.
Si vous utilisez deux du même maître, choisissez le même maître.

La configuration SPI spécifique n'a pas besoin de distinguer s'il est utilisé pour l'envoi ou la réception. Il suffit de configurer le SPI utilisé et les broches d'activation associées selon le schéma.

3.2.1 Configuration SPI1

La carte de développement atom Mini ponctuelle utilise SPI1 pour la communication, et le schéma de principe est illustré sur la figure.
insérez la description de l'image ici
Ici, SPI1 est utilisé pour la communication.
insérez la description de l'image ici

NRF24L01 nécessite une fréquence d'horloge ne dépassant pas 8Mhz.

Pour les trois broches restantes, IRQ correspond à une entrée pull-up, et la ligne de commande et la sélection de puce sont des sorties push-pull.
insérez la description de l'image ici
En même temps, configurez la redirection du port série pour la réception, ce qui est pratique pour l'observation.
===>>>Configuration du port série<<<===

3.2.2 Configuration SPI2

La carte de développement Puzhong-Zhunrui-Z100 utilise SPI2 pour la communication, et le schéma de principe est illustré sur la figure.
insérez la description de l'image ici
Ici, SPI2 est utilisé pour la communication.
insérez la description de l'image ici
Pour les trois broches restantes, IRQ correspond à une entrée pull-up, et la ligne de commande et la sélection de puce sont des sorties push-pull.
insérez la description de l'image ici
En même temps, configurez la redirection du port série pour la réception, ce qui est pratique pour l'observation.
===>>>Configuration du port série<<<===

Notez que si les deux utilisent deux SPI différents, par exemple, SPI1 et SPI2 sont utilisés ici, car les horloges des deux SPI sont différentes, de sorte que les coefficients de division de fréquence ne peuvent pas être les mêmes, et la fréquence et d'autres informations après la configuration doivent être c'est garanti c'est pareil.

SPI1 de STM32 est sur APB2, SPI2 et SPI3 sont sur APB1, la fréquence la plus élevée d'APB1 est de 36 MHz et la fréquence la plus élevée d'APB2 est de 72 MHz.

3.3. Mise en œuvre du code

3.3.1. Ajouter un code conducteur

Après avoir généré le projet, ajoutez le code pilote de NRF24L01 au projet (les deux projets sont requis).
===>>>Code conducteur (aucun point requis, téléchargez directement)<<<===

icodeCréez un dossier dans le répertoire du projet , créez NRF24L01-y un dossier et ajoutez nrf24L01.h- nrf24l01.cy des fichiers (les deux projets sont requis).
insérez la description de l'image ici
Ajoutez le chemin du code et du fichier d'en-tête dans Keil (de nombreux articles précédents ont été écrits, je ne montrerai donc pas l'image ici).

3.3.2 Modification du pilote

En fait, il n'y a pas beaucoup de modifications. L'expéditeur et le destinataire sont les mêmes.

  1. Dans nrf24L01.hle fichier, modifiez le SPI que vous utilisez.
  2. Dans nrf24L01.hle fichier, modifiez la définition des broches CS, CE et IRQ. Si vous utilisez le même Label que moi dans la configuration graphique, vous n'avez pas besoin de le modifier.
  3. Dans nrf24L01.cle fichier, modifiez quel SPI est utilisé (un seul endroit sur le schéma).
    insérez la description de l'image ici
    insérez la description de l'image ici

3.4. Fonction principale

expéditeur

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

Destinataire

/* 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. Tests

Testez d'abord l'extrémité émettrice.La
carte de développement est connectée à l'assistant de débogage du port série.
Réinitialisez la carte de développement.

  1. Si le module n'est pas branché au début, il vérifiera toujours si le module existe et demandera qu'il n'existe pas 0 ;
  2. Une fois la détection trouvée, une invite est émise ;
  3. Ensuite, le module continue d'envoyer des données et donne une invite d'envoi réussi ;
  4. Qu'il soit reçu ou non, il sera toujours envoyé.

insérez la description de l'image ici
Ensuite, testez l'extrémité de réception.
Éteignez d'abord l'extrémité d'envoi et n'allumez que l'extrémité de réception. Vous pouvez voir que les étapes d'initialisation sont les mêmes que ci-dessus. Mais une fois l'initialisation terminée, il attendra de recevoir des données.
insérez la description de l'image ici
Ensuite, ouvrez l'expéditeur, vous pouvez recevoir les données envoyées par l'expéditeur.
insérez la description de l'image ici
Débranchez le module émetteur et les données ne pourront plus être reçues.

Remettez le code du pilote, bienvenue pour l'aimer ! ! ! ===>>>Code conducteur (aucun point requis, téléchargez directement)<<<===

Après avoir travaillé sur ce module pendant un jour et demi, j'ai enfin fini d'écrire ce chapitre du blog.

Je suppose que tu aimes

Origine blog.csdn.net/weixin_46253745/article/details/127851093
conseillé
Classement