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