STM32_11(SPI)

SPI通信

  • SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
  • 四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)
  • MOSI:是主设备输出、从设备输入的数据信号线;、MISO:主设备输入、从设备输出的数据信号线; 
  • 同步,全双工
  • 支持总线挂载多设备(一主多从)

 

SPI硬件电路

  • 所有SPI设备的SCK、MOSI、MISO分别连在一起
  • 主机另外引出多条SS控制线,分别接到各从机的SS引脚
  • 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
  • 主机只能选择一个从机,选择多个从机就会导致数据冲突。当从机的SS引脚为高电平,就是从机未被选中,它的MISO引脚必须切换为高阻态,也就是把引脚断开。这样就可以防止一条线有多个输出,而导致的电平冲突问题。

 

移位示意图

移位寄存器有一个时钟输入端,因为SPI都是高位先行,所以每来一个时钟,移位寄存器就会向左移位,从机的移位寄存器同理。移位寄存器的时钟源是由主机提供的,这里叫做波特率发生器,它产生的时钟驱动主机的移位寄存器进行移位。同时,这个时钟也通过SCK引脚进行输出,接到从机的移位寄存器里。主机移位寄存器左边移出去的数据通过MOSI引脚,输入到从机的移位寄存器的右边,从机移位寄存器的数据根据MISO输入到主机移位寄存器的右边。

首先规定,波特率发生器时钟上升沿,所有移位寄存器向左移动一位,移出去的位放在引脚上,波特率发生器时钟的下降沿,引脚上的位,采样输入到移位寄存器的最低位。

 

SPI时序基本单元

起始条件和终止条件

  • 起始条件:SS从高电平切换到低电平
  • 终止条件:SS从低电平切换到高电平

 

模式0

  • 交换一个字节(模式0)
  • CPOL=0:空闲状态时,SCK为低电平
  • CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

总体来说,MOSI和MISO都提前SCK半个周期。

 

模式1

  • 交换一个字节(模式1)
  • CPOL=0:空闲状态时,SCK为低电平
  • CPHA=1(时钟相位:决定第一个时钟采样移入还是第二个时钟采样移入):SCK第一个边沿移出数据,第二个边沿移入数据

SS高电平时,MISO用一条中间的线表示高阻态,SS下降沿之后,从机的MISO被允许开启输出。SS上升沿之后,从机的MISO必须置回高阻态。当SCK为上升沿时,主机和从机同时移除数据,主机通过MOSI移除最高位,此时的MOSI的电平就表示主机要发送数据B7,从机通过MISO移除最高位,此时MISO就表示从机要发送数据B7。之后时钟运行,产生下降沿,此时的主机和从机需同时移入数据,也就是数据采样,这里主机移除B7,进入从机移位寄存器的最低位,从机移除B7,进去主机移位寄存器的最低位。一个时钟脉冲产生完毕,一位数据传输完毕。

 

SPI时序(发送指令)

在这里我们使用的是模式0,首先SS是高电平,SCK是低电平。SS产生下降沿,时序开始,在下降沿时刻,MOSI和MISO就开始比变换数据,MOSI的指令码仍为0,所以保持低电平不变,MISO从机没有数据发给主机,引脚电平没有变换。从机采样输入,得到0,主机采样输入,得到1。之后主机要发送数据1,SCK下降沿,数据移出,主机将1移出到MOSI,MOSI变高电平。当主机发送0时候,SCK下降沿,MOSI变为0。SCK上升沿,数据采样,从机接收数据为0。总体来说,SCK低电平时变化时期,高电平时读取时期(下降沿变换数据,上升沿采样数据)。

这个SPI时序代表,主机用0x06换来了从机的0xFF。

 

SPI时序(指定地址写)

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

 

SPI时序(指定地址读)

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

 

SPI外设

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

 

SPI框图

首先,移位寄存器右边低位的数据一位一位地从MOSI移出,MISO的数据一位一位地以到做左边移位寄存器的数据高位。这个可以控制是低位先行还是高位先行。

假如我们需要连续发送一串数据,第一个数据写入发送缓冲区(TDR),当移位寄存器没有数据移位时,TDR的数据就会立刻转入移位寄存器,开始移位,这个转入时刻,会置状态寄存器的TXE为1(表示发送寄存器空),紧接着,下一个数据就可以提前写入TDR等待,一旦上个数据发送完成,下一个数据就可以立刻跟进,实现不间断的连续传输。当数据到移位寄存器后,它就会自动产生时钟,将数据移出去,在移除的过程中,MISO数据也会移入。一旦数据移除完成,数据移入也完成。这时,移入的数据就整体从移位寄存器转入到接收缓冲区RDR,这时会将状态寄存器RXNE置1(表示接收寄存器非空)。当我们检测RXNE置1后,就需要尽快把数据从RDR读出来,在下个数据来之前就可以读出RDR ,实现连续接收。

 

SPI基本结构

 

主模式全双工连续传输(效率更高)

 

非连续传输(代码友好)

步骤:

1、等待TXE置1;

2、写入数据到TDR寄存器;

3、等待RXNE为1;

4、读取RDR接收的数据。

 

W25Q64模块

  • 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硬件电路

硬件原理图

/CS(/代表低电平有效或者CS上面有一横线也是低电平有效。

这里的CS对应SS,DI对应MOSI,DO对应MISO。

 

Flash操作注意事项

  • 写入操作时:
  • 写入操作前,必须先进行写使能
  • 每个数据位只能由1改写为0,不能由0改写为1
  • 写入数据前必须先擦除,擦除后,所有数据位变为1
  • 擦除必须按最小擦除单元进行(最小的擦除单元是一个扇区4096字节,最大的能把全部擦除)
  • 连续写入多字节时,最多写入一页(256字节)的数据,超过页尾位置的数据,会回到页首覆盖写入
  • 写入操作结束后,芯片进入忙状态,不响应新的读写操作
  • 读取操作时:直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

 

代码部分

SPI软件配置代码

#include "Bsp_SPI.h"

/* SS写数据 */
void Bsp_SPI_W_SS(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

/* SCK写数据 */
void Bsp_SPI_W_SCK(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}

/* MOSI写数据 */
void Bsp_SPI_W_MOSI(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}

/* MISO读数据 */
uint8_t Bsp_SPI_R_MISO(void)
{
    return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}

/* SPI初始化 */
void Bsp_SPI_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_Init(GPIOA, &GPIO_InitStructure);

    Bsp_SPI_W_SS(1);
    Bsp_SPI_W_SCK(0);

}

/* SPI起始 */
void Bsp_SPI_Start(void)
{
    Bsp_SPI_W_SS(0);
}

/* SPI终止 */
void Bsp_SPI_Stop(void)
{
    Bsp_SPI_W_SS(1);
}

/* MOSI和MISO交换字节 */
/* 模式0 */
uint8_t Bsp_SPI_SwapByte(uint8_t ByteSend)
{
    uint8_t ByteReceive = 0x00;

    for (uint8_t i = 0; i < 8; i++)
    {
        Bsp_SPI_W_MOSI(ByteSend & (0x80 >> i));
        Bsp_SPI_W_SCK(1);
        if (Bsp_SPI_R_MISO() == 1)
        {
            ByteReceive |= (0x80 >> i);
        }
        Bsp_SPI_W_SCK(0);
    }

    return ByteReceive;
}

/* 模式1 */
/*uint8_t Bsp_SPI_SwapByte(uint8_t ByteSend)
{
    uint8_t ByteReceive = 0x00;

    for (uint8_t i = 0; i < 8; i++)
    {
        Bsp_SPI_W_SCK(1);
        Bsp_SPI_W_MOSI(ByteSend &(0x80 >> i));
        Bsp_SPI_W_SCK(0);
        if (Bsp_SPI_R_MISO == 1)
        {
            ByteReceive |= (0x80 >> i);
        }
    }

    return ByteReceive;
}*/

/* 模式3 */
/*uint8_t Bsp_SPI_SwapByte(uint8_t ByteSend)
{
    uint8_t ByteReceive = 0x00;

    for (uint8_t i = 0; i < 8; i++)
    {
        Bsp_SPI_W_SCK(0);
        Bsp_SPI_W_MOSI(ByteSend &(0x80 >> i));
        Bsp_SPI_W_SCK(1);
        if (Bsp_SPI_R_MISO == 1)
        {
            ByteReceive |= (0x80 >> i);
        }
    }

    return ByteReceive;
}*/

/* 模式2 */
/*uint8_t Bsp_SPI_SwapByte(uint8_t ByteSend)
{
    uint8_t ByteReceive = 0x00;

    for (uint8_t i = 0; i < 8; i++)
    {
        Bsp_SPI_W_MOSI(ByteSend &(0x80 >> i));
        Bsp_SPI_W_SCK(0);
        if (Bsp_SPI_R_MISO == 1)
        {
            ByteReceive |= (0x80 >> i);
        }
        Bsp_SPI_W_SCK(1);
    }

    return ByteReceive;
}*/

 

W25Q64地址封装

#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

 

软件SPI读取W25Q64

#include "Bsp_W25Q64.h"

void W25Q64_Init(void)
{
    Bsp_SPI_Init();
}

/* 读取ID */
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
    Bsp_SPI_Start();
    Bsp_SPI_SwapByte(W25Q64_JEDEC_ID);               // 发送0x9F指令码
    *MID = Bsp_SPI_SwapByte(W25Q64_DUMMY_BYTE);      // 返回制造厂商ID
    *DID = Bsp_SPI_SwapByte(W25Q64_DUMMY_BYTE);      // 返回设备ID的高8位
    *DID <<= 8;                         
    *DID |= Bsp_SPI_SwapByte(W25Q64_DUMMY_BYTE);     // 返回设备ID的低8位
    Bsp_SPI_Stop();
}

/* 使能 */
void W25Q64_WriteEnable(void)
{
    Bsp_SPI_Start();
    Bsp_SPI_SwapByte(W25Q64_WRITE_ENABLE);         
    Bsp_SPI_Stop();
}

/* 读状态寄存器1(主要读取BUSY位,判断是否在忙) */
void W25Q64_WaitBusy(void)
{
    uint32_t TimeOut = 100000;

    Bsp_SPI_Start();
    Bsp_SPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);   
    while ((Bsp_SPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)        // 判断状态寄存器1的最低位,也就是BUSY位是否在忙。0:忙     1:在忙
    {
        TimeOut--;
        if (TimeOut == 0)
        {
            break;
        }
    }
    Bsp_SPI_Stop();
}

/* 页编程 */
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataAarry, uint16_t Length)
{
    W25Q64_WriteEnable();                       // 使能
    Bsp_SPI_Start();
    Bsp_SPI_SwapByte(W25Q64_PAGE_PROGRAM);
    Bsp_SPI_SwapByte(Address >> 16);
    Bsp_SPI_SwapByte(Address >> 8);
    Bsp_SPI_SwapByte(Address);

    for (uint16_t i = 0; i < Length; i++)
    {
        Bsp_SPI_SwapByte(DataAarry[i]);
    }
    Bsp_SPI_Stop();

    W25Q64_WaitBusy();
}

/* 扇区擦除 */
void W25Q64_SectorErase(uint32_t Address)
{
    W25Q64_WriteEnable();                       // 使能
    Bsp_SPI_Start();
    Bsp_SPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
    Bsp_SPI_SwapByte(Address >> 16);
    Bsp_SPI_SwapByte(Address >> 8);
    Bsp_SPI_SwapByte(Address);
    Bsp_SPI_Stop();

    W25Q64_WaitBusy();
}

/* 读数据 */
void W25Q64_ReadData(uint32_t Address, uint8_t *DataAarry, uint32_t Length)
{
    Bsp_SPI_Start();
    Bsp_SPI_SwapByte(W25Q64_READ_DATA);
    Bsp_SPI_SwapByte(Address >> 16);
    Bsp_SPI_SwapByte(Address >> 8);
    Bsp_SPI_SwapByte(Address);

    for (uint32_t i = 0; i < Length; i++)
    {
        DataAarry[i] = Bsp_SPI_SwapByte(W25Q64_DUMMY_BYTE);
    }
    Bsp_SPI_Stop();
}

 

硬件SPI读取W25Q64

#include "Bsp_SPI.h"

/* SS写数据 */
void Bsp_SPI_W_SS(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

/* SPI初始化 */
void Bsp_SPI_Init(void)
{
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    SPI_InitTypeDef SPI_InitStructure;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;                    // 分频系数,分频越高速度越快(注意:SPI1是72M,SPI2是36M。因为SPI1是在APB2,SPI2是在APB1)
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;                                            // 选择第一个边沿采样还是第二个边沿采样
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                                              // 时钟极性:因为在这里选择模式0
    SPI_InitStructure.SPI_CRCPolynomial = 7;                                                // CRC校验多项式,默认值7
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                                       // 8位数据帧
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;                      // 双线全双工模式
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                                      // 高位先行
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                                           // 选择向前设备时SPI主机
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                                               // NSS引脚,一般没用到选择软件NSS
    SPI_Init(SPI1, &SPI_InitStructure);

    SPI_Cmd(SPI1, ENABLE);

    Bsp_SPI_W_SS(1);

}

/* SPI起始 */
void Bsp_SPI_Start(void)
{
    Bsp_SPI_W_SS(0);
}

/* SPI终止 */
void Bsp_SPI_Stop(void)
{
    Bsp_SPI_W_SS(1);
}

/* MOSI和MISO交换字节 */
/* 模式0 */
uint8_t Bsp_SPI_SwapByte(uint8_t ByteSend)
{
    uint32_t TimeOut = 10000;
    uint8_t ByteReceive;
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != 1)
    {
        TimeOut--;
        if (TimeOut == 0)
        {
            break;
        } 
    }
    SPI_I2S_SendData(SPI1, ByteSend);
    TimeOut = 10000;
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != 1)
    {
        TimeOut--;
        if (TimeOut == 0)
        {
            break;
        } 
    }
    ByteReceive = SPI_I2S_ReceiveData(SPI1);
    return  ByteReceive;
}

 

主程序 

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Bsp_W25Q64.h"

uint8_t MID;
uint16_t DID;
uint8_t ArrayWrite[] = {0x11, 0x22, 0x33, 0x44};
uint8_t ArrayRead[4];

int main(void)
{

    OLED_Init();                        
    W25Q64_Init();

    /* 显示厂商和设备ID号 */
    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, 13, DID, 4);

    W25Q64_SectorErase(0x000000);                    // 只要末尾3个十六进制数为0,那肯定是扇区的起始地址(不擦除,掉电不丢失)。如果不擦除则改写则会数据出错,因为只能1变0不能0变1的操作所以修改数据前得擦除。
    W25Q64_PageProgram(0x00000, ArrayWrite, 4);      // 写入数据ArrayWrite,并且写入的数据不能跨页

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

 

posted @ 2023-05-22 10:29  烟儿公主  阅读(143)  评论(0编辑  收藏  举报