0x0C SPI

本文的大部分内容来自B站up主 江协科技, 此文只供本人学习记录用途, 侵删

一、SPI

  • SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
  • 四根通信线:
  • SCK(Serial Clock) 时钟线
  • MOSI(Master Output Slave Input) 主机接收从机发送
  • MISO(Master Input Slave Output) 主机发送从机接收
  • SS(Slave Select) 从机选择
  • 同步,全双工支持总线挂载多设备(一主多从)
  • 大部分只当从机的设备, MOSI可能会叫DI, MISO叫DO, SS叫CS

I2C协议v2.1规定了100K,400K和3.4M三种速率(bps)。
SPI是一种事实标准,由Motorola开发,并没有一个官方标准。已知的有的器件SPI已达到50Mbps。

二、SPI硬件电路与通讯原理

  • 所有SPI设备的SCK、MOSI、MISO分别连在一起
  • 主机另外引出多条SS控制线,分别接到各从机的SS引脚
  • 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
  • 在从机未被选中时, MISO必须配置为高阻态(防止电源短路)

5

交换一个字节的基本原理(模式一):

SPI的发送接收的 基本单元就是数据交换, 时钟由主机的波特率发生器提供, 并接到从机

  • 当时钟上升沿来临时, 主机移出最高位放到MOSI, 从机移出最高位到MISO
  • 当时钟下降沿来临时, 主机接收MISO的数据放到最低位 , 从机接收MOSI的数据放到最低位

经历了8个循环, 主机和从机移位寄存器中的数据就被交换了

  • 当只想发送数据时, 只管把数据放到主机, 然后移位, 不用管接收的数据
  • 当只想接收数据时, 随便写个00或者FF到主机, 移位后读取接收的数据就行

5

三、SPI基本时序

SPI有两个可配置的位,:

  • CPOL 决定了空闲时SS是高电平还是低电平 CPOL =1代表高电平 CPOL =0代表低电平

  • CPHA :

    ​ CPHA =0 SCK第一个边沿移入数据,第二个边沿移出数据

    ​ CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

假设CPOL =1

起始条件和终止条件就是这样:

5

接下来看看两个可配置位的四种模式组合:

5

5

image-20231103210902793

image-20231103210910069

模式0下发送0x06 ( 0 0 0 0 0 1 1 0 ):

image-20231103211120838

四、W25QXX

  • W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景
  • 存储介质:Nor Flash(闪存)
  • 时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
  • 存储容量(24位地址):
  • W25Q40: 4Mbit / 512KByte
  • W25Q80: 8Mbit / 1MByte
  • W25Q16: 16Mbit / 2MByte
  • W25Q32: 32Mbit / 4MByte
  • W25Q64: 64Mbit / 8MByte
  • W25Q128: 128Mbit / 16MByte
  • W25Q256: 256Mbit / 32MByte

本博客使用的是W25Q64, 注意本博客的重点是SPI通信协议, 至于发送的命令是什么含义, 只在注释中简单解释, 不做详解

五、软件SPI代码

控制W25Q64实现读取ID号, 写入数据, 读出数据等操作

首先是软件SPI(模式1)的代码:

#include "stm32f10x.h"                  // Device header
void MYSPI_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启时钟
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //PB0(SS) PB10(SCK) PB11(MOSI)推挽输出
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
	GPIO_Init(GPIOB,&GPIO_InitStructure);//PB1(MISO) 浮空输入
	
	SS(1);  //闲置SS
	SCK(0); //关闭时钟
}
//以下为四个写(SS SCK MOSI)或读(MISO) SPI的四条通讯线的函数
void SS(uint8_t flag){GPIO_WriteBit(GPIOB,GPIO_Pin_0,(BitAction)flag);}
void SCK(uint8_t flag){GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)flag);}
void MOSI(uint8_t flag){GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)flag);}
uint8_t MISO(void){return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1);}
//SPI通讯 开始和结束的函数 开始时拉低SS 结束时拉高SS
void MYSPI_Start(void){SS(0);}
void MYSPI_Over(void){SS(1);}

//SPI交换一个字节, 注意这里没有完全模拟出一个移位寄存器同时收发的场景, 而是用了两个变量存放
uint8_t MYSPI_Swap(uint8_t sendByte){
	uint8_t receiveByte=0x00;
    //循环8次
	for(uint8_t i = 0;i < 8;i++){
        //主机将sendByte的数据移动到MOSI上
		MOSI(sendByte & (0x80>>i) );
		SCK(1);
        //读取MISO的数据放到receiveByte上
		if( MISO() == SET)
			receiveByte | = 0x80>>i;
		SCK(0);
	}
	return receiveByte;
}

驱动W25Q64

#include "stm32f10x.h"                  // Device header
#include "MYSPI.h"
//初始化
void W25Q64_Init(void){
	MYSPI_Init();
}
//读取厂商ID和设备ID
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID){
	MYSPI_Start();
	MYSPI_Swap(0x9f);
	*MID=MYSPI_Swap(0);
	*DID=MYSPI_Swap(0);
	*DID<<=8;
	*DID|=MYSPI_Swap(0);
	MYSPI_Over();
}
//写使能
void W25Q64_WriteEnable(void){
	MYSPI_Start();
	MYSPI_Swap(0x06);
	MYSPI_Over();
}
//等待忙状态结束
void W25Q64_WaitBusy(void){
	MYSPI_Start();
	MYSPI_Swap(0x05);
	while((MYSPI_Swap(0)&0x01)==1);
	MYSPI_Over();
}
//页编程(想指定地址写入数据 输入长度可以连续搬运)
void W25Q64_PageProgram(uint32_t Address,uint8_t * Data,uint8_t length){
	W25Q64_WriteEnable();
	MYSPI_Start();
	MYSPI_Swap(0x02);
	MYSPI_Swap(Address>>16);
	MYSPI_Swap(Address>>8);
	MYSPI_Swap(Address);
	for(int i=0;i<length;i++)MYSPI_Swap(Data[i]);
	MYSPI_Over();
	W25Q64_WaitBusy();
}
//擦除扇区数据
void W25Q64_SectorErase(uint32_t Address){
	MYSPI_Start();
	MYSPI_Swap(0x20);
	MYSPI_Swap(Address>>16);
	MYSPI_Swap(Address>>8);
	MYSPI_Swap(Address);
	MYSPI_Over();
}
//读取数据
uint8_t W25Q64_ReadData(uint32_t Address){
	uint8_t receiveData;
	MYSPI_Start();
	MYSPI_Swap(0x03);
	MYSPI_Swap(Address>>16);
	MYSPI_Swap(Address>>8);
	MYSPI_Swap(Address);
	receiveData= MYSPI_Swap(0);
	MYSPI_Over();
	return receiveData;
}

main函数中的使用

uint8_t Data[2] = {0x81,0x66}; //待写入地址
W25Q64_SectorErase(0x000001); //写入前先擦除
W25Q64_PageProgram(0x000001,Data,2); //向地址1 2 写入0x81 0x66

六、STM32中的SPI外设

  • STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
  • 可配置8位/16位数据帧、高位先行/低位先行时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
  • 支持多主机模型、主或从操作可精简为半双工/单工通信
  • 支持DMA
  • 兼容I2S协议
  • STM32F103C8T6 硬件SPI资源:SPI1、SPI2

看到简易框图, 还是TDR, RDR, TXE, RXNE 老熟人了

交换一个字节:

  1. 数据写入TDR, 如果TXE=1,直接进入移位寄存器开始移位, 移位过程中RXNE=0 TXE=0
  2. 交换完成后, 移位寄存器中的数据进入RDR, 并将RXNE=1

image-20231103223837846

七、硬件SPI驱动W25Q64

#include "stm32f10x.h"                  // Device header
void W25Q64_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5|GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure); //PA5(SPI1_SCK) PA7(SPI1_MOSI) 复用推挽
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
	GPIO_Init(GPIOA,&GPIO_InitStructure);//PA6(SPI1_MISO) 浮空输入
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;
	GPIO_Init(GPIOA,&GPIO_InitStructure);//PA4(SPI1_NSS) SS还是GPIO方便点
	
	SPI_InitTypeDef SPI_InitStructure;
	SPI_StructInit(&SPI_InitStructure);//初始化用不到的参数
	
    SPI_InitStructure.SPI_Mode=SPI_Mode_Master;
    SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
   	SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;
    SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;
	SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;
   	SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low;
	SPI_InitStructure.SPI_CPHA=SPI_CPHA_1Edge;
	SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;
    /*
    	SPI_Mode_Master; 设置单片机为主机
    	SPI_Direction_2Lines_FullDuplex; 裁剪引脚的参数 双线全双工(不裁剪)
    	SPI_DataSize_8b; 8位数据帧
    	SPI_FirstBit_MSB; 高位先行(LSB是低位先行)
    	SPI_BaudRatePrescaler_128; SCK的分频系数(72MHz/128)
    	SPI_CPOL_Low; CPOL=0
    	SPI_CPHA_1Edge; CPHA=1
    	(配置成了模式1)
    	SPI_NSS_Soft; SS用GPIO软件模拟
    */
	SPI_Init(SPI1, &SPI_InitStructure);
	SPI_Cmd(SPI1,ENABLE);//使能SPI1
}
//开始和结束的函数 开始时拉低SS 结束时拉高SS
void W25Q64_Start(void){GPIO_ResetBits(GPIOA,GPIO_Pin_4);}
void W25Q64_Over(void){GPIO_SetBits(GPIOA,GPIO_Pin_4);}
//交换字节
uint8_t W25Q64_Swap(uint8_t sendByte){
	uint8_t receiveByte;
    //等待TXE=1并发送数据
	while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) !=SET);
	SPI_I2S_SendData(SPI1, sendByte);
    //等待RXNE=1并接收数据
	while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) !=SET);
	receiveByte=SPI_I2S_ReceiveData(SPI1);
	return receiveByte;
}
//以下代码功能和软件SPI驱动的版本完全相同 不做解释
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID){
	W25Q64_Start();
	W25Q64_Swap(0x9f);
	*MID=W25Q64_Swap(0);
	*DID=W25Q64_Swap(0);
	*DID<<=8;
	*DID|=W25Q64_Swap(0);
	W25Q64_Over();
}
void W25Q64_WriteEnable(void){
	W25Q64_Start();
	W25Q64_Swap(0x06);
	W25Q64_Over();
}
void W25Q64_WaitBusy(void){
	W25Q64_Start();
	W25Q64_Swap(0x05);
	while((W25Q64_Swap(0)&0x01)==1);
	W25Q64_Over();
}
void W25Q64_PageProgram(uint32_t Address,uint8_t * Data,uint8_t length){
	W25Q64_WriteEnable();
	W25Q64_Start();
	W25Q64_Swap(0x02);
	W25Q64_Swap(Address>>16);
	W25Q64_Swap(Address>>8);
	W25Q64_Swap(Address);
	for(int i=0;i<length;i++){
		W25Q64_Swap(Data[i]);
    }
	W25Q64_Over();
	W25Q64_WaitBusy();
}
void W25Q64_SectorErase(uint32_t Address){
	W25Q64_Start();
	W25Q64_Swap(0x20);
	W25Q64_Swap(Address>>16);
	W25Q64_Swap(Address>>8);
	W25Q64_Swap(Address);
	W25Q64_Over();
}
uint8_t W25Q64_ReadData(uint32_t Address){
	uint8_t receiveData;
	W25Q64_Start();
	W25Q64_Swap(0x03);
	W25Q64_Swap(Address>>16);
	W25Q64_Swap(Address>>8);
	W25Q64_Swap(Address);
	receiveData= W25Q64_Swap(0);
	W25Q64_Over();
	return receiveData;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值