Directorio de artículos
====>>> 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 )
- Desarrollado por Motorola
- Bus de comunicación síncrono, dúplex completo y de alta velocidad
- se requieren cuatro cables
- 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.
1.3 Generar proyecto
1.3.1 Crear maestro de selección de proyectos
1.3.2 Configuración del sistema
Configure la fuente del reloj
Configure el modo de depuración (puede verificarlo si necesita descargar y depurar ST-Link)
Configurar el árbol del reloj (puede ingresar directamente 72 en HCLK y luego presionar Enter para configurarlo automáticamente)
1.3.3 Directorio del proyecto de configuración
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
del pin del chip:
- Pin de selección de chip CS. Nivel bajo significa seleccionado.
- Interfaz de salida de datos DO SPI
- 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.
- Tierra pública GND
- Interfaz de entrada de datos DI SPI
- Interfaz de reloj CLK SPI
- 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.
- 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)
.
- Todo el chip de 8M bytes se divide en 128 bloques, cada bloque es de 64kb;
- Cada bloque de 64k bytes se divide en 16 sectores, cada sector es de 4K bytes (4096 bytes);
- 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.
configuración SPI
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.
- Cree un archivo separado en la carpeta del proyecto
icode
para almacenar nuestro propio código. - En la carpeta icode, cree otra
W25Q64文件夹
para almacenar códigos relacionados con W25Q64. w25qxx.c
Cree dos archivos en la carpeta W25Q64w25qxx.h
.
Agregar rutas de archivo de origen y encabezado
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.c
prueba 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
3. Comunicación del módulo inalámbrico NRF24L01
3.1 Introducción al Módulo
Introducción
- es un módulo de comunicación inalámbrica;
- Trabaja en la banda de frecuencia libre y abierta de 2,4 GHz;
- La tasa de comunicación puede alcanzar hasta 2 Mbps;
- 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 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! ! !
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):
- CSN: la línea de selección de chip del chip, el chip funciona a bajo nivel;
- SCK: línea de reloj controlada por chip (reloj SPI);
- MISO: línea de datos de control de chip (MISO de SPI);
- MOSI: línea de datos de control de chip (MOSI de SPI);
- IRQ: señal de interrupción, el chip NRF24L01 recibe datos o envía datos, etc., generará una interrupción de borde descendente;
- 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.
Aquí SPI1 se utiliza para la comunicación.
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.
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.
Aquí SPI2 se utiliza para la comunicación.
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.
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)<<<===
icode
Cree una carpeta en el directorio del proyecto , cree NRF24L01
una carpeta en ella y agréguele nrf24L01.h
archivos nrf24l01.c
(ambos proyectos son obligatorios).
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.
- En
nrf24L01.h
el archivo, modifique qué SPI está utilizando. - En
nrf24L01.h
el 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. - En
nrf24L01.c
el archivo, modifique qué SPI se usa (solo un lugar en el diagrama).
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.
- Si el módulo no está enchufado al principio, siempre verificará si el módulo existe y le indicará que no existe 0;
- Después de encontrar la detección, se emite un aviso;
- Posteriormente, el módulo sigue enviando datos y da un aviso de envío exitoso;
- Tanto si se recibe como si no, siempre se enviará.
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.
Luego abra el remitente, puede recibir los datos enviados por el remitente.
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.