赞
踩
SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
四根通信线:
同步,全双工
支持总线挂载多设备(一主多从)
SS(Slave Select)从机选择线(可以不止一条),专门用来指定通信的从机地址,相比于I2C在起始条件后的寻址操作方便
没有应答机制
优点:最大传输速度取决于芯片厂商设计需求,比I2C最大400KHZ快
缺点:SPI硬件开销较大,通信线个数较多
与I2C协议相比,I2C协议由于上拉电阻和开漏输出模式,导致其输出高电平的能力弱,直观的:上升沿的耗时长,限制了I2C的最大通信速度
所有SPI设备的SCK、MOSI、MISO分别连在一起
主机另外引出多条SS控制线,分别接到各从机的SS引脚【低电平有效】
输出引脚配置为推挽输出(高低电平驱动能力好),输入引脚配置为浮空或上拉输入
由于SPI通信线都是单端信号, 需要共地
对比:I2C的输出引脚只能配置为复用开漏输出,而不能配置为推挽输出,因为是半双工要切换输入输出,且实现多主机的时钟同步和总线仲裁,配置为推挽输出容易电源短路。而SPI是只有一个主机,无总线总裁,且是全双工,不需要切换线的功能
当从机的SS引脚为高电平时,MISO必须切换为高阻态,这样引脚就不会输出任何电平,此时主机的MISO端口就不会接收到三个从机发送过来的信号,导致冲突


高位先行,和I2C一样
SCK低-高电平:主机和从机都会:移位输出
SCK高-低电平:主机和从机都会:采样输入
SPI的数据收发都是基于字节交换这个基本单元进行的
如果只要读取从机数据时:主机可以发送0X00或0XFF去和从机换数据
波特率发生器:即移位寄存器里面的时钟,是由主机提供的,通过SCK外接到从机,保持两个设备的同步
中间还少画了输出数据寄存器,保存一位数据,作为SCK高低电平转换时候存储数据的中转站
起始条件:SS从高电平切换到低电平
终止条件:SS从低电平切换到高电平

因为SS是低电平,表示选中一个从机,高电平表示结束从机的选中状态,通信结束
可以配置两个位,时钟极性和时钟相位
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

SCK第一个边沿之前就需要移出数据,可以理解为在第零个边沿移出数据,那么在什么时候移出数据呢,此时不是用SCK线作为参考,而是使用SS线,在SS变为低电平的时候就开始移出数据
CPOL=0:空闲状态时,SCK为低电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

ps:MISO一条线表示高阻态
MOSI和MISO出现X表示数据的移动
CPOL=1:空闲状态时,SCK为高电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

CPOL=1:空闲状态时,SCK为高电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

通常采用:指令码+读写数据的模型,起始条件后第一个字节为指令码,在从机中会有指令集,指导从机完成相应功能,指令集也定义了该指令需要携带什么数据
举例W25Q64中指令的使用:
向SS指定的设备,发送指令(0x06),主机用0x06换来从机的0xFF

向SS指定的设备,发送写指令(0x02),随后在指定地址(Address[23:0])下,写入指定数据(Data)

向SS指定的设备,发送读指令(0x03),随后在指定地址(Address[23:0])下,读取从机数据(Data)

W25Q64: 64Mbit / 8MByte习惯使用字节作为基本单元,所以W25Q64的64表示的是64M(位),即8M字节

写保护:配置寄存器配置,实现对芯片的写操作失败
数据保持:当需要使用SPI外设完成其他任务时,ss置高电平,此时会丢失时序,通过HOLD引脚,替SPI外设控制该从机的ss为低电平,此时时序就不会丢失【相当于spi总线进入中断,保存了上下文】

关于地址:
Control Logic:SPI控制逻辑,负责将外部主控芯片传入的指令和数据进行处理
状态寄存器负责记录:写保护,数据保持,忙状态等
写控制逻辑:与外部的wp引脚相连,实现写保护(连接状态寄存器)
高电压发生器:flash掉电不丢失的存储器一般都有高压源,使得数据保持在某个状态
页地址锁存/计数器:保存前两个字节的地址,通过写保护和行解码找到对应的页。计数器负责地址加一操作
字节地址锁存/计数器:保存最后一个字节的地址,通过列解码和256字节缓存找到对应的地址。计数器负责地址加一操作
256字节缓存:RAM存储器,因为SPI高频,而存储空间操作是低频的,需要缓冲。也规定了连续写入的数据量不能超过256字节,数据从缓冲区写入flash时会将状态设置为忙(有连接状态寄存器)
芯片ID:

指令集:


写入操作时:
写入操作前,必须先进行写使能每个数据位只能由1改写为0,不能由0改写为1,所以要先执行擦除指令写入数据前必须先擦除,擦除后,所有数据位变为1读取操作时:

MySPI.c
#include "stm32f10x.h" // Device header void MySPI_W_SS(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); } void MySPI_W_SCK(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); } void MySPI_W_MOSI(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); } uint8_t MySPI_R_MISO(void) { return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); } void MySPI_Init(void) { 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); //初始化后将片选信号置高电平,设置SPI为模式0 MySPI_W_SS(1); MySPI_W_SCK(0); } //开始的时序 void MySPI_Start(void) { MySPI_W_SS(0); } //结束的时序 void MySPI_Stop(void) { MySPI_W_SS(1); } //模式0,置换一个字节的时序 uint8_t MySPI_SwapByte(uint8_t ByteSend) { uint8_t i, ByteReceive = 0x00; for (i = 0; i < 8; i ++) { //使用其他模式就是按不同的顺序执行下列代码,以及修改SCK的初始值 MySPI_W_MOSI(ByteSend & (0x80 >> i)); MySPI_W_SCK(1); if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} MySPI_W_SCK(0); } return ByteReceive; }
MySPI.h
#ifndef __MYSPI_H
#define __MYSPI_H
void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);
#endif
W25Q64.c
#include "stm32f10x.h" // Device header #include "MySPI.h" #include "W25Q64_Ins.h" void W25Q64_Init(void) { MySPI_Init();//底层spi时序初始化 } //根据指令集写功能函数 void W25Q64_ReadID(uint8_t *MID, uint16_t *DID) { MySPI_Start(); MySPI_SwapByte(W25Q64_JEDEC_ID); //不同时序调用得到的返回值是不同的 *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); *DID <<= 8; *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); MySPI_Stop(); } //写使能 void W25Q64_WriteEnable(void) { MySPI_Start(); MySPI_SwapByte(W25Q64_WRITE_ENABLE); MySPI_Stop(); } //直接读取寄存器的busy位,如果为0则表示不忙,程序返回,否则会阻塞 void W25Q64_WaitBusy(void) { uint32_t Timeout; MySPI_Start(); MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); Timeout = 100000; while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) { Timeout --; if (Timeout == 0) { break; } } MySPI_Stop(); } //按页写入 void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count) { uint16_t i; W25Q64_WriteEnable();//写入操作前,必须先进行写使能 MySPI_Start(); MySPI_SwapByte(W25Q64_PAGE_PROGRAM); MySPI_SwapByte(Address >> 16); MySPI_SwapByte(Address >> 8); MySPI_SwapByte(Address); //Count不能定义为uint8_t ,因为最大是255字节,而不是256字节 for (i = 0; i < Count; i ++) { MySPI_SwapByte(DataArray[i]); } MySPI_Stop(); W25Q64_WaitBusy(); } //扇区擦除 void W25Q64_SectorErase(uint32_t Address) { W25Q64_WriteEnable();//写入操作前,必须先进行写使能 MySPI_Start(); MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); MySPI_SwapByte(Address >> 16); MySPI_SwapByte(Address >> 8); MySPI_SwapByte(Address); MySPI_Stop(); W25Q64_WaitBusy(); } //按页读取 void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count) { uint32_t i; MySPI_Start(); MySPI_SwapByte(W25Q64_READ_DATA); MySPI_SwapByte(Address >> 16); MySPI_SwapByte(Address >> 8); MySPI_SwapByte(Address); for (i = 0; i < Count; i ++) { DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//置换有用数据 } MySPI_Stop(); }
W25Q64.h
#ifndef __W25Q64_H
#define __W25Q64_H
void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);
#endif
W25Q64_Ins.h
#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
main.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "W25Q64.h" uint8_t MID; uint16_t DID; uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04}; 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) { } }
参考视频:江科大自化协
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。