stm32-SPI

SPI : serial peripheral interface

主机发送了多少数据,,从机就会返回多少数据??????

极性: 时钟极性,, cpol : clock polarity ,,, 他决定了,spi空闲时,,时钟线SCK默认是低电平还是高电平

  • 有些芯片喜欢时钟默认低
  • 有些芯片喜欢时钟默认高
  • spi为了兼容各种设别,于是允许配置时间空闲时高还是低

CPHA : clock phase : 时钟相位,, spi在第几个时钟边沿"采样数据"

  • 数据变化和数据读取不能同时发生,,,否则,数据还没稳定就被读取,,可能会错,,所以需要一个边沿改变数据,另外一个边沿,读取数据

第一边沿

第二边沿

SPI的四种模式

传输顺序

MSB : most significant bit

LSB : least significant bit

一个字节里面 bit怎么排,,,

数据宽度: spi总线每次传输bit位的个数 ,,, 8bit和16bit

do : 是 miso ,,, master input slave output ,, 接PA6

单片机作为主机

  • txe : transmit buffer empty : 发送缓冲区空,,表示可以写新的数据了
  • rxne : receive buffer not empty : 接收缓冲区非空,,表示收到数据了
  • ovr : overrun : 溢出

  • 2线全双工 : 两根数据线,同时发送和接受
  • 2线只读 : 虽然有两根线,,但是只接收

常用于stm32作为spi从机,,只负责上传数据,,或者读取flash

  • 1线发送: 只使用一根数据线,只发送,,, 比如驱动oled,,只需要显示
  • 1线接收: 1根数据线,只接收

主机1线接收,,从机1线发送,,,一个接受一个发送


根据从机设置SPI的属性,,,

SPI内部是 移位寄存器 shift register,,spi会放入移位寄存器,,每来一个时钟,,整个寄存器移一位

一个时钟周期,,spi传一个bit


作为从机的时候,,nss就是一个片选信号

多主机模式 :

spi总线有多个主机

主机1通过普通IO向 主机2 的NSS输入低电压,,, 主机2就变成了从机,,,,

多主机模式,,,nss被拉低时,,主机身份变成从机,,,

nss必须是高电压,,,否则主机身份就会丢失,,,,,,,


主机每发送一个比特位,,必然会从从机收到一个比特位

c 复制代码
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "buzzer.h"
#include "lightSensor.h"
#include "stdio.h"
#include "oled.h"
#include "counterSensor.h"
#include "encoder.h"
#include "timer.h"
#include "pwm.h"
#include "inputCapture.h"
#include "ad.h"
// direct memory access
#include "mydma.h"

uint16_t ad_value[4];

void app_onBoardLed_init(void){
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
		GPIO_InitTypeDef GPIO_initStruct = {0};
		// 操控引脚
		GPIO_initStruct.GPIO_Pin = GPIO_Pin_13;

		GPIO_initStruct.GPIO_Mode = GPIO_Mode_Out_OD;
		// 输入模式不用写最大输出速度
		GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
		// 初始化IO引脚,,
		GPIO_Init(GPIOC,&GPIO_initStruct);
}

// general purpose input output
void app_GPIOA_init(void){
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
		GPIO_InitTypeDef GPIO_initStruct = {0};
		// 操控引脚
		GPIO_initStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;

		GPIO_initStruct.GPIO_Mode = GPIO_Mode_AF_PP;
		// 输入模式不用写最大输出速度
		GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
		// 初始化IO引脚,,
		GPIO_Init(GPIOA,&GPIO_initStruct);
		
		
		
		GPIO_initStruct.GPIO_Pin = GPIO_Pin_6;

		GPIO_initStruct.GPIO_Mode = GPIO_Mode_IPU;
		// 输入模式不用写最大输出速度
		GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
		// 初始化IO引脚,,
		GPIO_Init(GPIOA,&GPIO_initStruct);
		
		
				
		GPIO_initStruct.GPIO_Pin = GPIO_Pin_4;

		GPIO_initStruct.GPIO_Mode = GPIO_Mode_Out_PP;
		// 输入模式不用写最大输出速度
		GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
		// 初始化IO引脚,,
		GPIO_Init(GPIOA,&GPIO_initStruct);

}

void app_spi1_init(void){

		RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	
		SPI_InitTypeDef spi_initStruct;
		
	// 是master 还是 从机
		spi_initStruct.SPI_Mode = SPI_Mode_Master;
		spi_initStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	
		spi_initStruct.SPI_DataSize = SPI_DataSize_8b;
		spi_initStruct.SPI_CPOL = SPI_CPOL_High;
		spi_initStruct.SPI_CPHA = SPI_CPHA_2Edge;
		spi_initStruct.SPI_FirstBit = SPI_FirstBit_MSB;
		spi_initStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
	// 软件控制nss,,nss必须为1才是主机,,否则是从机
		spi_initStruct.SPI_NSS = SPI_NSS_Soft;
	
		SPI_Init(SPI1,&spi_initStruct);
	
	
	// nss设置为1
		SPI_NSSInternalSoftwareConfig(SPI1,SPI_NSSInternalSoft_Set);
}





void app_spi_masterTransmitReceive(SPI_TypeDef *spix,const uint8_t *pDataTx,uint8_t *pDataRx,uint16_t size){


	// 闭合总开关
	SPI_Cmd(spix,ENABLE);
	
	SPI_I2S_SendData(spix,pDataTx[0]);
	
	for(uint16_t i=0;i< size - 1;i++){
	
		while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_TXE) == RESET);
		
		SPI_I2S_SendData(spix,pDataTx[i+1]);
		
		// 读取数据
		while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_RXNE) == RESET);
		
		pDataRx[i] = SPI_I2S_ReceiveData(spix);
		
	}
	
	
	// 读取最后一个字节的数据
	while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_RXNE) == RESET);
	pDataRx[size-1] = SPI_I2S_ReceiveData(spix);
	
	
	// 断开总开关
		SPI_Cmd(spix,DISABLE);
}



// 常规序列,, 单通道
int main(void)
{
	OLED_Init();



	while(1)
	{
	
	}
}

64Mbit ⇒ 8M byte ,

一个扇区

一个扇区划分多个 页 ,,,一个页,,,256byte


flash不能直接改 0 ---》 1.,,,, 而擦除操作通常是大范围的,,,所以必须划分,,页,扇区,块

w25Q64 : 属于 SPI NOR flash ,,, 外部nor flash,,

  • 页 page : 最小编程单位,,写入单位
    不可能一次性写入全部数据,,写一段就会停一下,写一段会停一下,,这里的一段就是一个页page,,,写的最小单位
  • 扇区 :4kb,,, 最常用的擦除单位,, 擦除速度还行,浪费空间小

flash来说,,安全性最重要,,,flash中有一把锁,,,将锁打开,,,,

扇区擦除 和 页编程 都需要时间,,,,

主机发 0x05, 回收到 状态寄存器1,, 8个bit位,,随后一位就是busy,,是否不空闲


spi协议只规定:

  • 时钟怎么走
  • bit怎么转
  • 什么时候采样

sck

mosi

miso

cpol

cpha

msb

一拍传1bit

spi只是规定 双方同时交换bit,,不是,双方数据必须相同


spi协议 底层

w25Q64通信协议 是上层

w25Q64规定,,收到什么之后,,后面怎么解释,,是返回寄存器值,,还是清除扇区

c 复制代码
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "buzzer.h"
#include "lightSensor.h"
#include "stdio.h"
#include "oled.h"
#include "counterSensor.h"
#include "encoder.h"
#include "timer.h"
#include "pwm.h"
#include "inputCapture.h"
#include "ad.h"
// direct memory access
#include "mydma.h"

uint16_t ad_value[4];

void app_onBoardLed_init(void){
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
		GPIO_InitTypeDef GPIO_initStruct = {0};
		// 操控引脚
		GPIO_initStruct.GPIO_Pin = GPIO_Pin_13;

		GPIO_initStruct.GPIO_Mode = GPIO_Mode_Out_OD;
		// 输入模式不用写最大输出速度
		GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
		// 初始化IO引脚,,
		GPIO_Init(GPIOC,&GPIO_initStruct);
}

// general purpose input output
void app_GPIOA_init(void){
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
		GPIO_InitTypeDef GPIO_initStruct = {0};
		// 操控引脚
		GPIO_initStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;

		GPIO_initStruct.GPIO_Mode = GPIO_Mode_AF_PP;
		// 输入模式不用写最大输出速度
		GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
		// 初始化IO引脚,,
		GPIO_Init(GPIOA,&GPIO_initStruct);
		
		
		
		GPIO_initStruct.GPIO_Pin = GPIO_Pin_6;

		GPIO_initStruct.GPIO_Mode = GPIO_Mode_IPU;
		// 输入模式不用写最大输出速度
		GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
		// 初始化IO引脚,,
		GPIO_Init(GPIOA,&GPIO_initStruct);
		
		
				
				// 对片选引脚,,写一个高电压,,, 后面变成低电压了,,才是读取从机
		GPIO_initStruct.GPIO_Pin = GPIO_Pin_4;

		GPIO_initStruct.GPIO_Mode = GPIO_Mode_Out_PP;
		// 输入模式不用写最大输出速度
		GPIO_initStruct.GPIO_Speed = GPIO_Speed_50MHz;
		// 初始化IO引脚,,
		GPIO_Init(GPIOA,&GPIO_initStruct);

		GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);

}

void app_spi1_init(void){
	
	app_GPIOA_init();

		RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	
		SPI_InitTypeDef spi_initStruct;
		
	// 是master 还是 从机
		spi_initStruct.SPI_Mode = SPI_Mode_Master;
		spi_initStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	
		spi_initStruct.SPI_DataSize = SPI_DataSize_8b;
		spi_initStruct.SPI_CPOL = SPI_CPOL_Low;
		spi_initStruct.SPI_CPHA = SPI_CPHA_1Edge;
		spi_initStruct.SPI_FirstBit = SPI_FirstBit_MSB;
		spi_initStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
	// 软件控制nss,,nss必须为1才是主机,,否则是从机
		spi_initStruct.SPI_NSS = SPI_NSS_Soft;
	
		SPI_Init(SPI1,&spi_initStruct);
	
	
	// nss设置为1
		SPI_NSSInternalSoftwareConfig(SPI1,SPI_NSSInternalSoft_Set);
		
		
			SPI_Cmd(SPI1,ENABLE);
}





void app_spi_masterTransmitReceive(SPI_TypeDef *spix,const uint8_t *pDataTx,uint8_t *pDataRx,uint16_t size){


//	// 闭合总开关
//	SPI_Cmd(spix,ENABLE);
	
	SPI_I2S_SendData(spix,pDataTx[0]);
	
	for(uint16_t i=0;i< size - 1;i++){
	
		while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_TXE) == RESET);
		
		SPI_I2S_SendData(spix,pDataTx[i+1]);
		
		// 读取数据
		while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_RXNE) == RESET);
		
		pDataRx[i] = SPI_I2S_ReceiveData(spix);
		
	}
	
	
	// 读取最后一个字节的数据
	while(SPI_I2S_GetFlagStatus(spix,SPI_I2S_FLAG_RXNE) == RESET);
	pDataRx[size-1] = SPI_I2S_ReceiveData(spix);
	
	
//	// 断开总开关
//		SPI_Cmd(spix,DISABLE);
	

}




void app_w25Q64_saveByte(uint8_t byte){
		uint8_t buffer[10];
		buffer[0] = 0x06;
	
	// 写使能   spi发送 0x06  ===》 write enable ,,, 
	// 一旦真正执行写或者擦除后,,会自动关闭写使能,,,后面还需要重新打开
		GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);
	
		app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);
		GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);
	
	
	// 扇区擦除  0x20 + 24位地址
	buffer[0] = 0x20;
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);

	app_spi_masterTransmitReceive(SPI1,buffer,buffer,4);
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);
	
	
	// 等待空闲
	
	while(1){
		buffer[0] = 0x05;
		// 激活从机
		GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);

		// 发送0x05获取寄存器数据  ===> 不同命令后面跟的数据格式不同,,是协议
		app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);
	
		// 获取寄存器的值
		buffer[0] = 0xff;
		app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);
	
		
		GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);
		
		
		// 不忙  ===》 清除扇区结束了
		if((buffer[0] & 0x01) == 0){
			break;
		}
	
	}
	
	
	// 写使能 ===》 防止误写,每次写操作前,必须明确授权一次
		GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);
	
		buffer[0] = 0x06;
		app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);
		GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);
	
	
	// 页编程  0x02 写数据  + 24位地址 ===》 数据要写到哪个位置
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);

	buffer[0] = 0x02;
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	buffer[4] = byte;
	
	app_spi_masterTransmitReceive(SPI1,buffer,buffer,5);
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);
	
	
	
		
	// 等待空闲
	
	while(1){
		buffer[0] = 0x05;
		// 激活从机
		GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);

		// 发送0x05获取寄存器数据  ===> 不同命令后面跟的数据格式不同,,是协议
		app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);
	
		// 获取寄存器的值
		buffer[0] = 0xff;
		app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);
	
		
		GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);
		
		
		// 不忙  ===》 清除扇区结束了
		if((buffer[0] & 0x01) == 0){
			break;
		}
	
	}
	
}


uint8_t app_w25Q64_loadByte(void){
		uint8_t buffer[10];
	
		buffer[0] = 0x03;
		buffer[1] = 0x00;
		buffer[2] = 0x00;
		buffer[3] = 0x00;

		// 0x03读取数据
		GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_RESET);
	
		app_spi_masterTransmitReceive(SPI1,buffer,buffer,4);
	
	// 接受一个字节
		buffer[0] = 0xff;  
		app_spi_masterTransmitReceive(SPI1,buffer,buffer,1);
		GPIO_WriteBit(GPIOA,GPIO_Pin_4,Bit_SET);
	
	
		return buffer[0];
}




void test_w25q64_id(void)
{
    uint8_t tx[4];
    uint8_t rx[4];

    tx[0] = 0x9F;
    tx[1] = 0xFF;
    tx[2] = 0xFF;
    tx[3] = 0xFF;

    GPIO_ResetBits(GPIOA, GPIO_Pin_4);

    app_spi_masterTransmitReceive(SPI1, tx, rx, 4);

    GPIO_SetBits(GPIOA, GPIO_Pin_4);

    OLED_ShowHexNum(1,1,rx[1],2);
    OLED_ShowHexNum(2,1,rx[2],2);
    OLED_ShowHexNum(3,1,rx[3],2);
}

uint8_t a =0;

// 常规序列,, 单通道
int main(void)
{
	OLED_Init();
OLED_ShowString(1,1,"hehe:");
	app_spi1_init();
	
	
	// test_w25q64_id();
	
	app_w25Q64_saveByte(0x12);
	a = app_w25Q64_loadByte();

	OLED_ShowHexNum(2,1,(uint32_t)a,4);

	while(1)
	{
	
	}
}
相关推荐
QiLinkOS2 小时前
从技术到资产的跃迁:企业专利布局的深层逻辑
c语言·数据结构·c++·单片机·嵌入式硬件·算法·开源
夜听莺儿鸣2 小时前
201_002 Zynq7000 SoC PS资源介绍
嵌入式硬件·硬件架构
wohoo_wangzi3 小时前
苏州晟雅泰电子:关于汽车领域会用到的5类存储芯片,容量参数、设计方案和主要应用场景
嵌入式硬件·汽车
踏着七彩祥云的小丑3 小时前
嵌入式测试学习第 22 天:仿真看简易电路,熟悉电路运行逻辑
单片机·嵌入式硬件
czhaii4 小时前
基于51单片机的Modbus从机通信系统
开发语言·单片机
普中科技5 小时前
【普中STM32F1xx开发攻略--标准库版】-- 第 40 章 FSMC-TFTLCD 显示实验
stm32·单片机·嵌入式硬件·fsmc·开发板·tftlcd·普中科技
woohuwan5 小时前
功率线与信号线共模电感的核心区别
嵌入式硬件
LCG元6 小时前
STM32实战:基于STM32F103的智能衣柜(除湿+防霉+照明)
stm32·单片机·嵌入式硬件
0南城逆流06 小时前
【STM32】RTT-Studio中HAL库开发教程十三:MSH串口组件
stm32·单片机·嵌入式硬件