11: STM32 --- comunicación spl

Tabla de contenido

1: comunicación SPL

1: reanudar

2: circuito de hardware

3: gráfico de datos móviles

4: Unidad básica de sincronización SPI

A: Condiciones de apertura/finalización

B: Unidad básica de sincronización SPI

R: Modo 0

B: Modo 1

C: Modo 2

D: Modo 3

C: sincronización SPl

R: enviar instrucciones

B: Especifique la dirección a escribir

C: Especificar dirección para leer

Dos: W25Q64

1: reanudar

2: circuito de hardware

3: diagrama de bloques W25Q64

4: Precauciones de operación del flash

5: conjunto de instrucciones

Tres: Caso

R: Software SPI de lectura y escritura W25Q64

1: diagrama de conexión

2: Código

B: Hardware SPI de lectura y escritura W25Q64

1: reanudar

2: diagrama de bloques

3: estructura básica SPI

4: Transmisión continua full-duplex en modo principal

5: Transmisión discontinua

6: diagrama de conexión

7: código 


1: comunicación SPL

1: reanudar


          SPI (Serial Peripheral Interface) es un bus de datos universal desarrollado por Motorola

        Cuatro líneas de comunicación: SCK (Reloj serie), MOSI (Salida maestra Entrada esclava), MISO (Entrada maestra Salida esclava), SS (Selección de esclavo)

         Síncrono (con línea de reloj), full duplex (2 líneas de transmisión, líneas de envío y recepción)

        Admite el montaje de múltiples dispositivos en el bus (un maestro, múltiples esclavos)

        SP1 no tiene mecanismo de respuesta

2: circuito de hardware

        El SCK, MOSI y MISO de todos los dispositivos SPI están conectados entre sí.

        El host también conduce múltiples líneas de control SS, que están conectadas a los pines SS de cada esclavo.

        Los pines de salida se configuran como salidas push-pull y los pines de entrada se configuran como entradas flotantes o pull-up.

        SS también se llama señal de selección de chip CS : está conectada a todos los esclavos y se utiliza para seleccionar con qué esclavo se comunica el maestro. Está activo a bajo nivel, el SS (CS) de cada esclavo está conectado al SSX del maestro. SS es para la máquina host, es la señal de salida y para la máquina esclava, es la señal de entrada.

       Configuración IO : Todas se realizan con STM32 como protagonista: Configuración de la señal de salida del host --- salida push-pull, configuración de la señal de entrada del host -- entrada flotante o pull-up

        SCK : Línea de reloj, la línea de reloj está completamente controlada por el host, por lo que para el host, la línea de reloj es una salida; para todos los esclavos, la línea de reloj es una entrada; de esta manera, el reloj síncrono del maestro puede ser enviado a cada esclavo.

        MOSI : salida de host, entrada esclava

        MISO  : entrada de host, salida esclava

       Con respecto a la entrada del host CS y MISO, salida del esclavo : cuando el esclavo no está seleccionado, es decir, el nivel del segmento SS es 1; la entrada del host MISO del esclavo, la salida del esclavo debe cambiarse a un estado de alta impedancia y el El estado de alta impedancia es equivalente a que el pin se desconecte y no emita ningún nivel; esto puede evitar el problema de conflicto de niveles causado por múltiples salidas en una línea; MISO solo puede convertirse en una salida push-pull cuando SS tiene un nivel bajo ---- Operación de la máquina esclava ------- Generalmente, solo necesitamos escribir el programa de la máquina host y no necesitamos preocuparnos por el programa de la máquina esclava.

3: gráfico de datos móviles

Intercambiar datos, MSB primero

La transmisión y recepción de datos SPI se basan en el intercambio de bytes, esta unidad básica (modelo de cambio)

        Primero, especificamos que el flanco ascendente del reloj del generador de velocidad en baudios hace que tanto el maestro como el esclavo desplacen datos hacia afuera; el flanco inferior del reloj mueve datos hacia adentro;

  

        Los datos se mueven de izquierda a derecha, por lo que el bit alto va primero. Primero, el reloj del generador de velocidad en baudios genera un flanco ascendente. El host coloca sus datos de bits más altos en MOSI y el esclavo coloca sus datos de bits más altos en MISO. arriba; datos se mueve hacia adentro en el flanco descendente generado por el generador de velocidad de bits; los datos de bits más altos del esclavo en la línea de datos MISO se colocan por encima de la posición más baja del maestro; los datos de bits más altos del maestro en MOSI se colocan en la posición más baja posición del esclavo

4: Unidad básica de sincronización SPI

A: Condiciones de apertura/finalización

        Condición inicial: SS cambia del nivel alto al nivel bajo

        Condición de terminación: SS cambia del nivel bajo al nivel alto

B: Unidad básica de sincronización SPI

Bajo cualquier operación, solo controlamos el host (solo escribimos el código del host) y el esclavo opera automáticamente (no es necesario escribir el código del esclavo) 

Lo que usamos frecuentemente es el modo 0.

R: Modo 0

        Intercambiar un byte (modo 0)

        CPOL=0: en estado inactivo, SCK tiene un nivel bajo

        CPHA = 0: el primer borde de SCK transfiere datos hacia adentro y el segundo borde transfiere datos hacia afuera.

        El maestro y el esclavo ingresan datos sincrónicamente en el flanco ascendente de SCL; el maestro y el esclavo transfieren datos sincrónicamente en el flanco descendente de SCL


/**
* @brief  SPL交换数据--使用的为模式0
	DI(MOSI)----SPI主机输出从机输入
	DO(MISO)-------SPI主机输入从机输出
	我们只操作主机:首先主机移出最高位,放在MOSI上面,---主机操作需要我们来
								齐次从机把数据放在MISO上面----从机的操作不需要我们管

* @param  ByteSend: 主机给从机发送的数据
	
  * @retval 主机读取的数据----即从机给主机发送的数据
  */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{		
	
	MySPI_W_SCK(0);
	//一般来说&是用来清零的;
//一般来说|是用来值一的;
	uint8_t ByteReceive=0x00;
	for (uint8_t i=0;i<8;i++)
	{
		MySPI_W_MOSI(ByteSend & (0x80>>i)); //MOSI主机输出数据 1000 0000 
		/*
		我们只操作主机: 
		SCL上升沿主机和从机同步移入数据, 从机会自动把主机给它的最高为移动到了从机里面---从机不需要我们操作
		主机操作 : 主机需要把从机给它发送的数据移动到了主机里面---即读取MISO线上的数据
		*/
		MySPI_W_SCK(1);
		if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}//MySPI_R_MISO主机读取数据
		MySPI_W_SCK(0);
		//SCL下降沿主机和从机同步移出数据
		//|---置1
	
	}
	return ByteReceive;
}

Bajo cualquier operación, solo controlamos el host (solo escribimos el código del host) y el esclavo opera automáticamente (no es necesario escribir el código del esclavo) 

B: Modo 1

        Intercambiar un byte (modo 1)

        CPOL=0: en estado inactivo, SCK tiene un nivel bajo

        CPHA = 1: el primer borde de SCK desplaza los datos hacia afuera y el segundo borde desplaza los datos hacia adentro

        Para configurar más chips de reloj, SPl nos proporciona 2 bits que podemos ajustar nosotros mismos, a saber: polaridad del reloj CPOL (polaridad del reloj) y configuración de fase del reloj CPHA (fase del reloj), que constituyen 4 patrones.

        Modo 1  : Tanto el maestro como el esclavo sacan datos en el flanco ascendente del reloj del generador de velocidad en baudios; los datos entran en el flanco inferior; el método de movimiento de datos en el Modo 1 es el mismo que en el diagrama 3: Movimiento de datos. Para obtener más información, consulte ---- 3: Imagen de datos en movimiento

C: Modo 2

        Intercambiar un byte (modo 2)

        CPOL=1: En estado inactivo, SCK es de nivel alto

        CPHA = 0: el primer borde de SCK transfiere datos hacia adentro y el segundo borde transfiere datos hacia afuera.

D: Modo 3

        Intercambiar un byte (modo 3)

        CPOL=1: En estado inactivo, SCK es de nivel alto

        CPHA = 1: el primer borde de SCK desplaza los datos hacia afuera y el segundo borde desplaza los datos hacia adentro

C: sincronización SPl

R: enviar instrucciones

Regulación: el primer byte a partir de SPL es el conjunto de instrucciones

enviar comando

Enviar comando (0x06) al dispositivo especificado por SS--0x06 enable

B: Especifique la dirección a escribir

        Especificar dirección para escribir

        Envíe un comando de escritura (0x02) al dispositivo especificado por SS, --- el conjunto de instrucciones escrito por 0x02    

        Luego escriba los datos especificados (Datos) en la dirección especificada (Dirección [23:0])   

        SPl no tiene mecanismo de respuesta: después de intercambiar un byte, comienza directamente a intercambiar el siguiente byte.

C: Especificar dirección para leer

        Especificar dirección para leer

        Envíe el comando de lectura (0x03) al dispositivo especificado por SS, --- conjunto de instrucciones 0x03 para enviar el comando   

        Luego lea los datos del esclavo (Datos) en la dirección especificada (Dirección [23:0])

Dos: W25Q64

1: reanudar

        La serie W25Qxx es una memoria no volátil de bajo costo, compacta y fácil de usar que se utiliza a menudo en almacenamiento de datos, almacenamiento de fuentes, almacenamiento de programas de firmware, etc.

        Medio de almacenamiento: Nor Flash (memoria flash)

        Frecuencia de reloj: 80MHz / 160MHz (Dual SPI) / 320MHz (Cuádruple SPI)

        Capacidad de almacenamiento (dirección de 24 bits):

        W25Q40: 4 Mbit/512 KB     

        W25Q80: 8 Mbit/1 MB   

         W25Q16: 16 Mbit/2 MB     

        W25Q32: 32 Mbit/4 MB   

         W25Q64: 64 Mbit/8 MB   

         W25Q128: 128 Mbit/16 MB   

         W25Q256: 256 Mbit / 32 MB

2: circuito de hardware

3: diagrama de bloques W25Q64

4: Precauciones de operación del flash

Memoria no volátil: no se pierde cuando se apaga la alimentación

Durante la operación de escritura :

        Antes de la operación de escritura, primero debe habilitar la escritura. ---------- Es una medida de protección para evitar un mal funcionamiento.

        Cada bit de datos solo se puede reescribir de 1 a 0, y no se puede reescribir de 0 a 1 --------------Flash no tiene la capacidad de sobrescribir y reescribir directa y completamente como la RAM. Por ejemplo: en una determinada unidad de almacenamiento directo, primero se almacena 0xaa 1010 1010 y luego se almacena 0x55 0101 0101. Debido a que Flash no tiene la capacidad de sobrescribir datos directamente, después de agregar las restricciones especificadas en el segundo artículo, los datos almacenados reales son: 0000 0000 , no 0x55, usado en escritura. Los datos anteriores deben borrarse antes de proporcionar los segundos datos.

        Antes de escribir datos, primero se deben borrar. Después de borrar, todos los bits de datos se convertirán en 1 --------------Existe un circuito de borrado especial para configurar los datos escritos previamente en 1 (0xFF) , que puede compensar las deficiencias del artículo 2

        El borrado debe realizarse de acuerdo con la unidad mínima de borrado. ------------No puedes especificar un determinado byte para borrar. Para borrar, tienes que borrar un área grande juntos. En nuestro chip, puedes Elija, para borrar todo el chip, también puede elegir borrar por bloque o por sector; la unidad de borrado más pequeña es un sector, que es de 4 KB o 4096 bytes.

        Al escribir varios bytes continuamente, se puede escribir un máximo de una página de datos. Los datos más allá del final de la página se devolverán a la parte superior de la página y se sobrescribirán. -------- Una secuencia de escritura solo puede escribir una página como máximo. Los datos son 256 bytes; un búfer de página tiene solo 256 bytes; la escritura de Flash es demasiado lenta. No puede mantener el ritmo de la frecuencia de SPI. Por lo tanto, los datos escritos se almacenarán temporalmente en la RAM primero. . Debes comenzar desde el principio de la página para escribir un máximo de 256 bytes. Si comienzas a escribir desde la dirección en el medio de la página, al escribir hasta el final de la página, la dirección saltará de regreso a la parte superior de la página, lo que causará confusión en la dirección.

        Una vez completada la operación de escritura, el chip entra en estado ocupado y no responde a nuevas operaciones de lectura y escritura . Para saber cuándo el chip finaliza el estado ocupado, podemos usar instrucciones para leer el registro de estado y observar el estado. Compruebe si el bit BUSY del registro es 1. Cuando el bit BUSY es 0, el chip no está ocupado y continuaremos la operación.

        Después de emitir el comando de borrado, el chip también entrará en el estado ocupado y tendremos que esperar a que finalice el estado ocupado antes de continuar con operaciones posteriores.

        El borrado de sector también es escritura, por lo que es necesario habilitarlo.

Durante la operación de lectura :

        Llame directamente a la secuencia de lectura, sin habilitación, sin operaciones adicionales, sin restricciones de página,

        No entrará en el estado ocupado una vez completada la operación de lectura, pero no se puede leer en el estado ocupado.

5: conjunto de instrucciones

NOMBRE DE INSTRUCCIÓN ---El nombre de la instrucción; BYTE----Byte X

 

Habilitación de escritura ---- conjunto de instrucciones de habilitación de escritura

Deshabilitar escritura -------- Conjunto de instrucciones de deshabilitar escritura

Leer registro de estado-1----------Leer registro de estado 1--Función: Determinar si el registro está ocupado. Para obtener más detalles, consulte la Parte 2: 4

Programa de página----------Programación de página, escritura de datos, máximo 256 bytes

Borrado de sector (4KB)------------Borrar según el sector de 4KB

ID JEDEC----------Leer ID

Leer datos-----Leer datos

Tres: Caso

R: Software SPI de lectura y escritura W25Q64

1: diagrama de conexión

        Debido a que estamos utilizando software para simular la comunicación SPL, en principio, los pines periféricos se pueden conectar al puerto 32 a voluntad y el puerto se usa para simular la comunicación SPL.

2: Código

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "mySPl.h"
#include "w25q64.h "

//一般来说&是用来清零的;
//一般来说|是用来值一的;
void MySPI_W_SS(uint8_t BitValue)
{
	//也叫做CS片选段----在低电平是有效
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);

}
void MySPI_W_SCK(uint8_t BitValue)
{
	//CLK(SCK)	SPI时钟
	GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);

}
void MySPI_W_MOSI(uint8_t BitValue)
{
	//MOSI-----主机输出
	GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);
}

uint8_t MySPI_R_MISO(void)
{
	//MISO-----主机输入
	return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}



/**
* @brief  DO(MISO)	SPI主机输入从机输出---连接的是PA6;   都是以主机的角度看
			输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
			PA6----浮空或上拉输入;  剩下的全部为推挽输出
  * @retval 无
  */
void MYSPL_init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5|GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  //上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	//在开始时候默认SS(CS)为高电平,SCK为低电平
	
	MySPI_W_SS(1);
	MySPI_W_SCK(0);
	
		
}


void SPL_Start()
{
	MySPI_W_SS(1);
	MySPI_W_SS(0);
}
void SPL_Stop()
{
	MySPI_W_SS(0);
	MySPI_W_SS(1);
}

/**
* @brief  SPL交换数据--使用的为模式0
	DI(MOSI)----SPI主机输出从机输入
	DO(MISO)-------SPI主机输入从机输出
	我们只操作主机:首先主机移出最高位,放在MOSI上面,---主机操作需要我们来
								齐次从机把数据放在MISO上面----从机的操作不需要我们管

* @param  ByteSend: 主机给从机发送的数据
	
  * @retval 主机读取的数据----即从机给主机发送的数据
  */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{		
	
	MySPI_W_SCK(0);
	//一般来说&是用来清零的;
//一般来说|是用来值一的;
	uint8_t ByteReceive=0x00;
	for (uint8_t i=0;i<8;i++)
	{
		MySPI_W_MOSI(ByteSend & (0x80>>i)); //MOSI主机输出数据 1000 0000 
		/*
		我们只操作主机: 
		SCL上升沿主机和从机同步移入数据, 从机会自动把主机给它的最高为移动到了从机里面---从机不需要我们操作
		主机操作 : 主机需要把从机给它发送的数据移动到了主机里面---即读取MISO线上的数据
		*/
		MySPI_W_SCK(1);
		if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}//MySPI_R_MISO主机读取数据
		MySPI_W_SCK(0);
		//SCL下降沿主机和从机同步移出数据
		//|---置1
	
	}
	return ByteReceive;
}



//W25Q64_WaitBusy等待不忙的函数, 事后等待,只需要在写入操作之后调用---因为在写入后等待不忙读取的时候肯定不忙
//而事前等待,在写入操作和读取操作之前,都得调用
//我们采用事前等待
void W25Q64_init()
{
	MYSPL_init();


}
/**
  * @brief  读取设备的ID号
步骤: 起始,先交换发送指令9F,随后连续交换接收3个字节,停止;
连续接收的3个字节: 第一个字节是广商ID”表示了是哪个广家生产.
后两个学节---是设备ID;    设备ID的高8为---表示存储器类型, 低8为--表示容量
* @param  MID : 输出8位的厂商ID
	@param  DID : 输出16位的设备ID

因为函数内的返回值只能返一个,而用指针,只要知道地址就可以写入;
可以直接改变外补的2个参数, 相当于返回2个参数

  * @retval 无
  */
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{	
	一般来说&是用来清零的;
	//一般来说|是用来值一的;
	SPL_Start();
	MySPI_SwapByte(0x9F);//先交换发送指令9F
	*MID = MySPI_SwapByte(0xFF);//返回的第一个字节是广商ID; 这时候我们给我从机发送的数据没有实际的意义
	*DID = MySPI_SwapByte(0xFF);//返回的第二个字节设备ID的高8为---表示存储器类型
	*DID <<= 8;
	*DID |= MySPI_SwapByte(0xFF);返回的第二个字节设备低8为--表示容量
	SPL_Stop();
}


/**
  * @brief  写使能函数
 
  */
void W25Q64_WriteEnable(void)
{
	SPL_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);//规定 : SPL起始的第一个字节为指令集
	SPL_Stop();
}

/**
* @brief  等待忙函数--状态寄存器1 :作用看寄存器忙不忙
       要想知道芯片什么时候结束忙状态了, 我们可以使用读取状态寄存器的指令,  
			看一下状态寄存器的BUSY位是否为1,  BUSY位为0时,芯片就不忙了,我们再进行操作


  */
void W25Q64_WaitBusy(void)
{		
		uint32_t Count;
		SPL_Start();
		MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//规定 : SPL起始的第一个字节为指令集
		Count=5000;
		while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) ==0x01)
			{
				Count--;
				if (Count==0)
				{
					break;
				}
			
			}
		SPL_Stop();
}
/**
  * @brief  写页编程-----主机给从机发送数据
	步骤 : 1---先发送指令;  2--然后连发3个字节,就是24位地址  3---之后继续发送
	DataByte1(数据1)、DataByte2、DataByte3, 最大是DataByte256
	* @param  Address 步骤中的1和2步---也就是发送的地址
* @param  *DataArray 主机给从机发送的数据, 这里面为一个数组
  * @param  Count 数组的长度
		
  * @retval 无
  */
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{	
	
	W25Q64_WaitBusy();//写入操作结束后,芯片进入忙状态,不响应新的读写操作
	W25Q64_WriteEnable();//写入操作前,必须先进行写使能
	SPL_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);//规定 : SPL起始的第一个字节为指令集
	MySPI_SwapByte(Address>>16); //一共为24位,把数据后移16为,先把前8为也就是第一个直接发送给从机
	MySPI_SwapByte(Address>>8);//发送给从机的第二给字节
	MySPI_SwapByte(Address);//发送给从机的第三给字节
	
	//下面为主机给从机发送的真正的数据
	for (uint8_t i=0; i<Count; i++)
	{
		
		MySPI_SwapByte(DataArray[i]);
	
	}
	SPL_Stop();

}


/**
  * @brief  按4KB的扇区擦除
  * @param  Address 擦除的地址
	步骤: 需要先发送指令0x20,再发送3个字节的地址,就行了
  * @retval 无
  */
void W25Q64_SectorErase(uint32_t Address)
{				
		W25Q64_WaitBusy();//在发出擦除指令后,芯片也会进入忙状态, 我们也得等待忙状态结束后,才能进行后续操作
		W25Q64_WriteEnable(); //扇区擦除也是写入所以需要使能
		SPL_Start();
		MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
		MySPI_SwapByte(Address>>16); //一共为24位,把数据后移16为,先把前8为也就是第一个直接发送给从机
		MySPI_SwapByte(Address>>8);//发送给从机的第二给字节
		MySPI_SwapByte(Address);//发送给从机的第三给字节
		SPL_Stop();

	
}


/**
	* @brief  主机接受从机给主机发送的数据
步骤:流程是,交换发送指令03,再发送3个字节地址;  随后转入接收,就可以依次接收数据了
  * @param  Address 起始行位置,范围:1~4
  * @param  DataArray 起始列位置,范围:1~16
  * @param  Count 要显示的数字,范围:0~1111 1111 1111 1111
  
  * @retval 无
  */
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{	
	W25Q64_WaitBusy();//读取操作结束后不会进入忙状态,但不能在忙状态时读取
	SPL_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);//规定 : SPL起始的第一个字节为指令集
	MySPI_SwapByte(Address>>16); //一共为24位,把数据后移16为,先把前8为也就是第一个直接发送给从机
	MySPI_SwapByte(Address>>8);//发送给从机的第二给字节
	MySPI_SwapByte(Address);//发送给从机的第三给字节
	
	//下面为主机正在接受的数据, 从机给主机发送数据
	for (uint8_t i=0; i<Count; i++)
	{
		
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //1111 1111 没有实际的意义
	
	}
		SPL_Stop();
}




#define W25Q64_WRITE_ENABLE							0x06
#define W25Q64_WRITE_DISABLE						0x04
#define W25Q64_READ_STATUS_REGISTER_1				0x05
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3

#define W25Q64_DUMMY_BYTE							0xFF  //这个数据实际没有意义

#endif




uint8_t MID;
uint16_t DID;

uint8_t ArrayWrite[] = {0x55, 0x66, 0x77, 0x88};
uint8_t ArrayRead[4];

int main(void)
{
	OLED_Init();
	W25Q64_init();
	
	OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
	
	W25Q64_ReadID(&MID, &DID);
	OLED_ShowHexNum(1, 5, MID, 2);
	OLED_ShowHexNum(1, 12, DID, 4);
	
	W25Q64_SectorErase(0x000000);//擦除
	W25Q64_PageProgram(0x000000, ArrayWrite, 4);//写页编程
	
	W25Q64_ReadData(0x000000, ArrayRead, 4);//读取
	
	OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);
	OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
	OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
	OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
	
	OLED_ShowHexNum(3, 3, ArrayRead[0], 2);
	OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
	OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
	OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
	
	while (1)
	{
	}
}

B: Hardware SPI de lectura y escritura W25Q64

1: reanudar

        STM32 integra un circuito transceptor SPI de hardware, que puede realizar automáticamente la generación de reloj, el transceptor de datos y otras funciones mediante el hardware, lo que reduce la carga de la CPU.

        Marco de datos configurable de 8 bits/16 bits, primero de gama alta/primero de gama baja

        Frecuencia de reloj: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)------PCLK es 72MHz en 32 indicando la velocidad del parámetro

        Soporta modelo multimaestro, operación maestra o esclava

        Se puede reducir a comunicación semidúplex/símplex

        Soporte DMA

        Compatible con protocolo I2S

        Recursos SPI de hardware STM32F103C8T6: SPI1, SPI2

2: diagrama de bloques

        La figura muestra primero el orden bajo, pero puede configurar primero el orden bajo o el alto nivel ajustando LSBFIRST. Generalmente, utilizamos el primer método de alto nivel.

        Búfer (de recepción y envío) ----- en realidad el registro de datos DR; el búfer de envío inferior es el registro de datos de envío TDR; el búfer de recepción superior es el registro de datos de recepción RDR; al igual que el puerto serie, TDR y RDR ocupan el La misma dirección se denomina colectivamente DR.

        TEX : El registro impar de datos y el registro de desplazamiento cooperan para lograr un flujo de datos continuo. El proceso específico es el siguiente: Los primeros datos se escriben en TDR. Cuando el registro de desplazamiento no tiene datos para desplazar, los datos TDR se transferirán inmediatamente a cambie el registro impar y comience a cambiar; en este momento de transferencia, TXE del registro de estado se establecerá en 1, lo que indica que el registro de transmisión está       vacío. Cuando verificamos que TXE está configurado en 1, los siguientes datos se pueden escribir con anticipación Espere a que se envíen los datos en el TDR y podrá seguir los siguientes datos inmediatamente.

       RXNE:   Luego aquí en el registro de desplazamiento, una vez que lleguen los datos, generará automáticamente un reloj y sacará los datos. Durante el proceso de salida, los datos de MISO (entrada maestra, salida esclava) también se moverán hacia adentro. Una vez los datos se sacan Completado, ¿se completó la transferencia de datos? En este momento, los datos transferidos se transferirán desde el registro de desplazamiento al búfer de recepción RDR en su conjunto. En este momento, el RXNE    del registro de estado se configurará en 1, lo que indica que el registro de recepción no está vacío.    Cuando verificamos que RXNE está configurado en 1, debemos leer los datos del RDR lo antes posible. Antes de que lleguen los siguientes datos, lea el RDR para lograr una recepción continua.

        

3: estructura básica SPI

4: Transmisión continua full-duplex en modo principal

Esto utiliza el modo 3 de la unidad básica de temporización SPI.

5: Transmisión discontinua

6: diagrama de conexión

        La conexión del hardware SPl debe conectarse según la tabla de definición de pines, no el software. Es el mismo método de conexión del hardware I2C, ambos se conectan según la tabla de definición de pines.

7: código 


#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "mySPl.h"
#include "w25q64.h "

//一般来说&是用来清零的;
//一般来说|是用来值一的;
void MySPI_W_SS(uint8_t BitValue)
{
	//也叫做CS片选段----在低电平是有效
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);

}

/**
* @brief  DO(MISO)	SPI主机输入从机输出---连接的是PA6;   都是以主机的角度看
			输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
			PA6----浮空或上拉输入;  剩下的全部为推挽输出
  * @retval 无
  */
void MYSPL_init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7| GPIO_Pin_5;//复用--控制的权力交给片上外设
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  //上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	//在开始时候默认SS(CS)为高电平,SCK为低电平
	
	SPI_InitTypeDef SPl_initstruct;
	SPl_initstruct.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;//波特率预分频器的值
	SPl_initstruct.SPI_CPHA=SPI_CPHA_1Edge;   //配置SPl的模式
	SPl_initstruct.SPI_CPOL=SPI_CPOL_Low;		//配置SPl的模式
	SPl_initstruct.SPI_CRCPolynomial=7;   //填入默认的7即可
	SPl_initstruct.SPI_DataSize=SPI_DataSize_8b;  //8个字节的大小
	SPl_initstruct.SPI_Direction=SPI_Direction_2Lines_FullDuplex;  //双线全双工
	SPl_initstruct.SPI_FirstBit=SPI_FirstBit_MSB;  //选择高位先行还是低位先行; --高位先行
	SPl_initstruct.SPI_Mode=SPI_Mode_Master;   //指定当前设备为主机还是从机; ---主机
	SPl_initstruct.SPI_NSS=SPI_NSS_Soft; //NSS使用软件模拟--软件模拟CS
	SPI_Init(SPI1,&SPl_initstruct);

	SPI_Cmd(SPI1,ENABLE);
	MySPI_W_SS(1);
		
}


void SPL_Start()
{
	MySPI_W_SS(1);
	MySPI_W_SS(0);
}
void SPL_Stop()
{
	MySPI_W_SS(0);
	MySPI_W_SS(1);
}

/**
* @brief  SPL交换数据--使用的为模式0
	DI(MOSI)----SPI主机输出从机输入
	DO(MISO)-------SPI主机输入从机输出
	我们只操作主机:首先主机移出最高位,放在MOSI上面,---主机操作需要我们来
								齐次从机把数据放在MISO上面----从机的操作不需要我们管

* @param  ByteSend: 主机给从机发送的数据
	
  * @retval 主机读取的数据----即从机给主机发送的数据
  */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{		
	//此标志为”1'时表明发送缓冲器为空,可以写下一个待发送的数据进入缓冲器中。
	//当写入SPI DR时,TXE标志被清除。
	while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET);//检查标志位
	
	SPI_I2S_SendData(SPI1,ByteSend);
	
	//此标志为'1时表明在接收缓冲器中包含有效的接收数据。读SPI数据寄存器可以清除此标志。
	while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET);//检查标志位

	return SPI_I2S_ReceiveData(SPI1);
}



//W25Q64_WaitBusy等待不忙的函数, 事后等待,只需要在写入操作之后调用---因为在写入后等待不忙读取的时候肯定不忙
//而事前等待,在写入操作和读取操作之前,都得调用
//我们采用事前等待
void W25Q64_init()
{
	MYSPL_init();


}
/**
  * @brief  读取设备的ID号
步骤: 起始,先交换发送指令9F,随后连续交换接收3个字节,停止;
连续接收的3个字节: 第一个字节是广商ID”表示了是哪个广家生产.
后两个学节---是设备ID;    设备ID的高8为---表示存储器类型, 低8为--表示容量
* @param  MID : 输出8位的厂商ID
	@param  DID : 输出16位的设备ID

因为函数内的返回值只能返一个,而用指针,只要知道地址就可以写入;
可以直接改变外补的2个参数, 相当于返回2个参数

  * @retval 无
  */
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{	
	一般来说&是用来清零的;
	//一般来说|是用来值一的;
	SPL_Start();
	MySPI_SwapByte(0x9F);//先交换发送指令9F
	*MID = MySPI_SwapByte(0xFF);//返回的第一个字节是广商ID; 这时候我们给我从机发送的数据没有实际的意义
	*DID = MySPI_SwapByte(0xFF);//返回的第二个字节设备ID的高8为---表示存储器类型
	*DID <<= 8;
	*DID |= MySPI_SwapByte(0xFF);返回的第二个字节设备低8为--表示容量
	SPL_Stop();
}


/**
  * @brief  写使能函数
 
  */
void W25Q64_WriteEnable(void)
{
	SPL_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);//规定 : SPL起始的第一个字节为指令集
	SPL_Stop();
}

/**
* @brief  等待忙函数--状态寄存器1 :作用看寄存器忙不忙
       要想知道芯片什么时候结束忙状态了, 我们可以使用读取状态寄存器的指令,  
			看一下状态寄存器的BUSY位是否为1,  BUSY位为0时,芯片就不忙了,我们再进行操作


  */
void W25Q64_WaitBusy(void)
{		
		uint32_t Count;
		SPL_Start();
		MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//规定 : SPL起始的第一个字节为指令集
		Count=5000;
		while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) ==0x01)
			{
				Count--;
				if (Count==0)
				{
					break;
				}
			
			}
		SPL_Stop();
}
/**
  * @brief  写页编程-----主机给从机发送数据
	步骤 : 1---先发送指令;  2--然后连发3个字节,就是24位地址  3---之后继续发送
	DataByte1(数据1)、DataByte2、DataByte3, 最大是DataByte256
	* @param  Address 步骤中的1和2步---也就是发送的地址
* @param  *DataArray 主机给从机发送的数据, 这里面为一个数组
  * @param  Count 数组的长度
		
  * @retval 无
  */
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{	
	
	W25Q64_WaitBusy();//写入操作结束后,芯片进入忙状态,不响应新的读写操作
	W25Q64_WriteEnable();//写入操作前,必须先进行写使能
	SPL_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);//规定 : SPL起始的第一个字节为指令集
	MySPI_SwapByte(Address>>16); //一共为24位,把数据后移16为,先把前8为也就是第一个直接发送给从机
	MySPI_SwapByte(Address>>8);//发送给从机的第二给字节
	MySPI_SwapByte(Address);//发送给从机的第三给字节
	
	//下面为主机给从机发送的真正的数据
	for (uint8_t i=0; i<Count; i++)
	{
		
		MySPI_SwapByte(DataArray[i]);
	
	}
	SPL_Stop();

}


/**
  * @brief  按4KB的扇区擦除
  * @param  Address 擦除的地址
	步骤: 需要先发送指令0x20,再发送3个字节的地址,就行了
  * @retval 无
  */
void W25Q64_SectorErase(uint32_t Address)
{				
		W25Q64_WaitBusy();//在发出擦除指令后,芯片也会进入忙状态, 我们也得等待忙状态结束后,才能进行后续操作
		W25Q64_WriteEnable(); //扇区擦除也是写入所以需要使能
		SPL_Start();
		MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
		MySPI_SwapByte(Address>>16); //一共为24位,把数据后移16为,先把前8为也就是第一个直接发送给从机
		MySPI_SwapByte(Address>>8);//发送给从机的第二给字节
		MySPI_SwapByte(Address);//发送给从机的第三给字节
		SPL_Stop();

	
}


/**
	* @brief  主机接受从机给主机发送的数据
步骤:流程是,交换发送指令03,再发送3个字节地址;  随后转入接收,就可以依次接收数据了
  * @param  Address 起始行位置,范围:1~4
  * @param  DataArray 起始列位置,范围:1~16
  * @param  Count 要显示的数字,范围:0~1111 1111 1111 1111
  
  * @retval 无
  */
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{	
	W25Q64_WaitBusy();//读取操作结束后不会进入忙状态,但不能在忙状态时读取
	SPL_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);//规定 : SPL起始的第一个字节为指令集
	MySPI_SwapByte(Address>>16); //一共为24位,把数据后移16为,先把前8为也就是第一个直接发送给从机
	MySPI_SwapByte(Address>>8);//发送给从机的第二给字节
	MySPI_SwapByte(Address);//发送给从机的第三给字节
	
	//下面为主机正在接受的数据, 从机给主机发送数据
	for (uint8_t i=0; i<Count; i++)
	{
		
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //1111 1111 没有实际的意义
	
	}
		SPL_Stop();
}


#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H

#define W25Q64_WRITE_ENABLE							0x06
#define W25Q64_WRITE_DISABLE						0x04
#define W25Q64_READ_STATUS_REGISTER_1				0x05
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3

#define W25Q64_DUMMY_BYTE							0xFF  //这个数据实际没有意义

#endif




uint8_t MID;
uint16_t DID;

uint8_t ArrayWrite[] = {0x55, 0x66, 0x77, 0x88};
uint8_t ArrayRead[4];

int main(void)
{
	OLED_Init();
	W25Q64_init();
	
	OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
	
	W25Q64_ReadID(&MID, &DID);
	OLED_ShowHexNum(1, 5, MID, 2);
	OLED_ShowHexNum(1, 12, DID, 4);
	
	W25Q64_SectorErase(0x000000);//擦除
	W25Q64_PageProgram(0x000000, ArrayWrite, 4);//写页编程
	
	W25Q64_ReadData(0x000000, ArrayRead, 4);//读取
	
	OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);
	OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
	OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
	OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
	
	OLED_ShowHexNum(3, 3, ArrayRead[0], 2);
	OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
	OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
	OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
	
	while (1)
	{
	}
}

Supongo que te gusta

Origin blog.csdn.net/m0_74739916/article/details/132857928
Recomendado
Clasificación