STM32 SPI通信

一.简介

SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线

四根通信线:SCK(Serial Clock串行时钟线)、MOSI(Master Output Slave Input主机输出从机输入或者叫做DO数据输出)、MISO(Master Input Slave Output主机输入从机输出DI数据输入)、SS(Slave Select从机选择(NSS、CS))

同步,全双工(收发可以同时)

支持总线挂载多设备(一主多从)

SCK:时钟线

MOSI和MISO分别用于发送和接收的两条线路,MOSI是主机输出从机输入,MISO是主机从从机接收数据的线路

SS:从机选择线,有几个主机就开几个SS选择线

引脚选择

以W25Q64为例

W25Q64是从机,从机的DI对应到主机STM32应该是MOSI,从机的DO对应到STM32应该是MISO

二.硬件电路

所有的MISO连在一起,所有的MOSI连在一起。

SS在一个时刻只能选择一个从机将其置0,要不然会产生冲突。

三.移位示意图

上升沿 :所有移位寄存器向左移动一位,移出去的放在引脚上

下降沿: 移出去的位放在最低位上

 四.SPI时序

四种交换模式 

CPHA是决定第几个边沿采用,并不能单独决定上升沿还是下降沿

 SCK要在第一个边沿到来之前就要先将数据移出去,等到第一个边沿到来的时候就可以直接移入数据。可以将SS的下降沿作为SCK的一部分,在SS下降沿到来的时候就可以将MOSI和MISO的数据移出去。

一开始SS没有选择具体的设备,所以从机设备要设置成高阻态,就对应从机的MISO电平处于中间位置(高阻态)。在SS置为高的时候,又要回到高阻态 。

在第一个边沿到来的时候,主机和从机同时移除最高位数据,在第二个边沿到来后移入数据。

 指令时序

SPI发送

SPI指定地址写 

SPI指定地址读

五.软件时序代码 

#include "stm32f10x.h"                  // Device header

void MySPI_W_SS(uint8_t BitValue)//从机选择
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}

void MySPI_W_MOSI(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);//MOSI
}

void MYSPI_W_SCK(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);//SCK
}

uint8_t MYSPI_R_MISO()//MISO
{
    return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}

void MySPI_Init()
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_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置高电平
    MySPI_W_SS(1);
    MYSPI_W_SCK(0);//使用模式0
}

void MySPI_Start()
{
    MySPI_W_SS(0);
}

void MySPI_Stop()
{
    MySPI_W_SS(1);
}

uint8_t MySPI_SwapByte(uint8_t ByteSend)//将主机接收到的数据,用ByteSend置换出来
{
    uint8_t ByteReceive=0x00;
    for(int i=0;i<8;i++)
    {
        MySPI_W_MOSI(ByteSend&(0x80>>i));//发送的是ByteSend的最高位
        MYSPI_W_SCK(1);
        if(MYSPI_R_MISO()==1)
        {
            ByteReceive|=(0x80>>i);//将最高位存在ByteReceive里面
        }
        MYSPI_W_SCK(0);
    }
    return ByteReceive;
}

六.驱动W25Q64 Flash存储器

1.简介

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

(易失性存储器:SRAM,DRAM,非易失性存储器:Flash,E2PROM主要是掉电丢不丢失)

W25Q64不能跨页写入,如果写入的数据跨页了,就从本页第一个重新写入,而不是跨页写入。如果确实有个很大的数组需要连续写入,只能自己从软件上,分批次进行写入,计算数组需要跨多少页,然后该擦除的擦除,分批次一页一写。

2.操作注意事项 

Flash是一个掉电不丢失的设备,为了保证掉电不丢失这个特性,所以会做出一些妥协让步。不像RAM那样简单直接,想在哪里写就在哪里写,想写多少就写多少,并且RAM是可以覆盖写入的。

写入操作时:

·写入操作前,必须先进行写使能

·每个数据位只能由1改写为0,不能由0改写为1

·写入数据前必须先擦除,擦除后,所有数据位变为1(弥补第二条缺陷)

·擦除必须按最小擦除单元进行

·连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入

·写入操作结束后,芯片进入忙状态,不响应新的读写操作

读取操作时:

·直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

3.SPI代码

#include "stm32f10x.h"                  // Device header

#define MySPI_W_SS(x) do{x?GPIO_SetBits(GPIOA,GPIO_Pin_4):GPIO_ResetBits(GPIOA,GPIO_Pin_4);}while(0)
#define MySPI_W_SCK(x) do{x?GPIO_SetBits(GPIOA,GPIO_Pin_5):GPIO_ResetBits(GPIOA,GPIO_Pin_5);}while(0)
#define MySPI_W_MOSI(x) do{x?GPIO_SetBits(GPIOA,GPIO_Pin_7):GPIO_ResetBits(GPIOA,GPIO_Pin_7);}while(0)

uint8_t MySPI_R_MISO()
{
    return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}
void MySPI_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);
    
    MySPI_W_SS(1);
    MySPI_W_SCK(0);
}

void MySPI_Start()
{
    MySPI_W_SS(0);
}

void MySPI_Stop()
{
    MySPI_W_SS(1);
}

uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
    uint8_t ByteReceive=0x00;
    for(uint8_t i=0;i<8;i++)
    {
        MySPI_W_MOSI(ByteSend&(0x80>>i));//主机移出数据
        MySPI_W_SCK(1);        
        if(MySPI_R_MISO()==1) ByteReceive|=(0x80>>i);//主机和从机同时移入数据
        MySPI_W_SCK(0);
    }
    return ByteReceive;
}

4.W25Q64代码

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"


void W25Q64_Init()
{
    MySPI_Init();
}

void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)//MID厂商ID,DID设备ID
{
    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()
{
    MySPI_Start(); 
    MySPI_SwapByte(W25Q64_WRITE_ENABLE);//发送写使能
    MySPI_Stop();    
}

void W25Q64_WaitBusy()
{
    uint32_t Timeout=100000;
    MySPI_Start(); 
    MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//读状态寄存器1
    while((MySPI_SwapByte(W25Q64_DUMMY_BYTE)&0x01)==1)
    {
        Timeout--;
        if(Timeout==0)break;//超时退出
    }//用掩码取出最低位,如果是等于0x01代表忙
    MySPI_Stop();  
}

//页编程,发送指令码+3个字节的地址+数据,这里的写数组的参数是输入参数,写的数据保存在数组
void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint16_t count)
{
    W25Q64_WriteEnable();//写入操作前,必须先进行写使能
    
    MySPI_Start(); 
    MySPI_SwapByte(W25Q64_PAGE_PROGRAM);//页编程
    MySPI_SwapByte(Address>>16);//取0x123456的0x12 1111 1111 1111 1111 1111 1111
    MySPI_SwapByte(Address>>8);
    MySPI_SwapByte(Address);
    
    for(uint16_t i=0;i<count;i++)
    {
        MySPI_SwapByte(DataArray[i]);
    }
    MySPI_Stop();  
    
    W25Q64_WaitBusy();//写入操作结束后,芯片进入忙状态
}

//扇区擦除 发送指令码+3个字节的地址
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)
{
    MySPI_Start();
    MySPI_SwapByte(W25Q64_READ_DATA);
    MySPI_SwapByte(Address>>16);
    MySPI_SwapByte(Address>>8);
    MySPI_SwapByte(Address);
    for(uint32_t i=0;i<count;i++)
    {
        DataArray[i]=MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    }
    MySPI_Stop();   
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值