【STC8H】全网最详细的SPI协议解析

八、SPI协议

(一)原理

SPI(Serial Peripheral Interface,串行外设接口) 协议是一种高速高效率、全双工的通信总线,允许CPU与低速的外围设备之间进行同步串行数据的传输,主要是用同步的时钟信号对串行的数据同时进行发送和接收操作,从而实现全双工。常用于短距离通讯,主要是在嵌入式系统中。

1.SPI接线

SPI接口在芯片内部只占用四根线,主要有两条数据线、一根片选线和一根时钟信号线。这四根线分别对应SPI协议传输时的四种信号,分别:

主机输出信号---------->MOSI

从机输入信号---------->MISO

片选信号---------------> CS(SS)

时钟信号---------------> SCK

SPI协议里定义了主模式从模式两种工作方式,主模式下工作的模块是主机,同理,从模式下的工作模块是从机。主机和从机之间按照SPI时序进行数据交换。

一个典型的SPI系统应包括只能有一个主机、一个或多个从机。下图为一主多从的工作场景:

名称                                                            作用
SCK时钟线                                                 主机输出从机的通讯时钟
MOSI主机输出                                           主机输出数据,从机接收数据
MISO从机输出                                           从机输出数据,主机接收数据
CS(SS)片选                                               主机控制与哪个从机通信 低电平有效

2.极性(CPOL)和相位(CPHA)

SPI相位和极性是指SPI通讯中的时钟信号的特性。时钟信号由主机发出,用来控制主从设备的数据交换的同步。

时钟的极性决定了总线空闲时的时钟电平。

时钟的相位决定了数据是在时钟的上升沿还是下降沿

读取不同的SPI设备可能需要不同的时钟极性和相位设置,以保证通讯的正确性。

(1)极性

表示时钟SCK空闲状态下,其电平值是低电平"0"还是高电平"1"

CPOL=0,时钟空闲时候是低电平,所以当SCK有效的时候,就是高电平

CPOL=1,时钟空闲时候是高电平,所以当SCK有效的时候,就是低电平

(2)相位

对应着数据采样是在第几个边沿,是第一个边沿还是第二个边沿,

0对应着第一个边沿

1对应着第二个边沿。

(3)极性相位组合

对于: CPHA=0,表示第一个边沿:

  • 对于CPOL=0,空闲时候的是低电平,第一个边沿就是从低变到高,所以是上升沿;

  • 对于CPOL=1,空闲时候的是高电平,第一个边沿就是从高变到低,所以是下降沿;

CPHA=1,表示第二个边沿:

  • 对于CPOL=0,空闲时候的是低电平,第二个边沿就是从高变到低,所以是下降沿;

  • 对于CPOL=1,空闲时候的是高电平,第一个边沿就是从低变到高,所以是上升沿;

img

对应的四种组合就是:

四个模式的参考代码:

//CPOL=0,CPHA=0
uint8_t uSPI_Byte_0(SPI_ObjectTypedef * hSPIx,uint8_t udata)
{
    uint8_t read, i;
    for(i=0;i<8;i++)
    {
        SPI_SCL_0(hSPIx);
        if(udata & 0x80)
        {
            SPI_MOSI_1(hSPIx);
        }
        else
        {
            SPI_MOSI_0(hSPIx);
        }
        SPI_SCL_1(hSPIx);
        udata<<=1;
        read<<=1;//接收数据默认为0,左移一位
        if(SPI_MISO_R(hSPIx))
        {
            read|=0x01;
        }
    }
    return read;
}
//CPOL=0,CPHA=1
uint8_t uSPI_Byte_1(SPI_ObjectTypedef * hSPIx,uint8_t udata)
{
    uint8_t read, i;
    
    for(i=0;i<8;i++)
    {
        SPI_SCL_1(hSPIx);//时钟线拉高,上升沿准备数据
        if( udata & 0x80 )
        {
            SPI_MOSI_1(hSPIx);
        }
        else
        {
            SPI_MOSI_0(hSPIx);
        }
        udata<<=1;//上一位已读取完,左移一位
        SPI_SCL_1(hSPIx);//数据准备完毕,时钟线拉高,开始采集数据
        read<<=1;//接收数据默认为0,左移一位
        if(SPI_MISO_R(hSPIx))
        {
            read|=0x01;
        }
    }
    return read;
}
//CPOL=1,CPHA=0
uint8_t uSPI_Byte_2(SPI_ObjectTypedef * hSPIx,uint8_t udata)
{
    uint8_t read, i;
    
    for(i=0;i<8;i++)
    {
        SPI_SCL_1(hSPIx);//时钟线拉高,上升沿准备数据
        if( udata & 0x80 )
        {
            SPI_MOSI_1(hSPIx);
        }
        else
        {
            SPI_MOSI_0(hSPIx);
        }
        SPI_SCL_0(hSPIx);//下降沿采集数据
        udata<<=1;//上一位已读取完,左移一位
        read<<=1;//接收数据默认为0,左移一位
        if(SPI_MISO_R(hSPIx))
        {
            read|=0x01;
        }
    }
    return read;
}
//CPOL=1,CPHA=1
uint8_t uSPI_Byte_3(SPI_ObjectTypedef * hSPIx,uint8_t udata)
{
    uint8_t read, i;
    
    for(i=0;i<8;i++)
    {
        SPI_SCL_0(hSPIx);//时钟线拉低,下降沿准备数据
        if( udata & 0x80 )
        {
            SPI_MOSI_1(hSPIx);
        }
        else
        {
            SPI_MOSI_0(hSPIx);
        }
        SPI_SCL_1(hSPIx);//数据准备完毕,时钟线拉高,开始采集数据
        udata<<=1;//上一位已读取完,左移一位
        read<<=1;//接收数据默认为0,左移一位
        if(SPI_MISO_R(hSPIx))
        {
            read|=0x01;
        }
    }
    return read;
}

当然,实际具体要用哪一种采集方式,需要查阅我们的芯片手册。

(二)通信过程

SPI不像IIC一样需要起始信号,结束信号,只要通过拉低某个片选信号选择连接的从机设备外,就可直接传输数据。

1.主机输出时钟信号

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATGluZXN0LTU=,size_20,color_FFFFFF,t_70,g_se,x_16

2.连接从机设备

主机将 SS/CS 引脚切换到低电平状态,来激活从机:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATGluZXN0LTU=,size_20,color_FFFFFF,t_70,g_se,x_16

3.主机发送数据

主机沿 MOSI 线路一次一位地将数据发送到从站。从机读取接收到的位:高位先读到

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATGluZXN0LTU=,size_20,color_FFFFFF,t_70,g_se,x_16

4.从机响应

如果需要响应,从机沿MISO线一次一位地将数据返回给主机。主机在接收到位时读取:低位先读到

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBATGluZXN0LTU=,size_20,color_FFFFFF,t_70,g_se,x_16

(三)Flah芯片----W25QXX

W25QXX是华邦公司生产的一块FLASH储存芯片。该芯片支持电平范围为2.7V-3.6V

1.引脚

2.极性相位模式

根据说明手册我们可以得知:

W25QXX的SPI传输支持方式0和方式3,即

模式0(CPOL=0,CPHA=0)

模式3(CPOL=1,CPHA=1)。

3.获取芯片的Flash ID

当电路搭建完成后,SPI通信基础函数编写完成后,下一步需要获取芯片返回一个值,测试电路与基础函数成功与否。

根据其说明手册,获取ID之前有两点需要注意:1.MSB高位优先传输,2.获取操作时CS必须拉低。

不同型号的ID号也有所不同。

img

根据下图的时序图:

  1. 获取时CS必须为低电平,高位优先传输。

  2. 获取 ID的指令为0x90。

  3. 接收到信息三个字节,第一个字节为ManufacturerID(制造商ID)固定值为0xEF,第二个字节为MemoryType(内存类型),第三个字节为Capacity(容量)。

//获取Flash ID
uint32_t W25Q32_Read_ID(SPI_ObjectTypedef * hSPIx)
{
    uint32_t read,read_1,read_2,read_3;
    SPI_CS_0(hSPIx);//片选置0
    SPI_SCL_0(hSPIx);
    
    uSPI_Byte_0(hSPIx,0x9F);//发送指令0x9f
    read_1=uSPI_Byte_0(hSPIx,0xFF);//接收flash ID
    read_2=uSPI_Byte_0(hSPIx,0xFF);//接收内存类型
    read_3=uSPI_Byte_0(hSPIx,0xFF);//接收容量
    
    SPI_CS_1(hSPIx);//片选置1
    read=(read_1<<16)|(read_2<<8)|read_3;//合并
    
    return read;
}

4.芯片写使能

写使能的操作与读ID的操作类似,其操作指令为0x06,写使能成功后Status Register1的S1位WSL置1,即允许写操作与擦除操作。

//写使能
void W25Q32_Write_Enable(SPI_ObjectTypedef * hSPIx)
{
    SPI_CS_0(hSPIx);//片选置0
    SPI_SCL_0(hSPIx);
    
    uSPI_Byte_0(hSPIx,0x06);//发送指令0x06
    
    SPI_CS_1(hSPIx);//片选置1,结束
}
​

5.忙等待操作

当芯片正在执行擦除、写操作时,不允许其它操作,此时必须进入等待状态,直到执行完成后Status Register1的S0位BUSY重新置0后才允许后续操作,函数必须不断的读取状态寄存器,从中提取S0的数据,从而确定相应操作的状态。Status Register1读取指令为0x05,读取方式为MSB ,那么S0位对应与操作位为0x01,操作方式与读ID的操作类似。

//等待保护(等待擦除或写入操作,直到操作完成结束)
void W25Q32_Wait_End(SPI_ObjectTypedef * hSPIx)
{
    uint8_t temp;
    
    SPI_CS_0(hSPIx);//片选置0
    SPI_SCL_0(hSPIx);
    
    uSPI_Byte_0(hSPIx,0x05);//发送指令0x05
    temp=uSPI_Byte_0(hSPIx,0xFF);
    while(temp&0x01==1)//当最低位为1,则继续等待
    {
        temp=uSPI_Byte_0(hSPIx,0xFF);
    }
    SPI_CS_1(hSPIx);//片选置1,结束
}

6.擦除指令

W25Q32为非易失性存储器,写数据之前必须先执行擦除操作

  1. 所谓的芯片擦除是往存储单元字节中填充0xFF即为擦除。

  2. 芯片擦除操作前必须开启Write Enable(写使能)之前有介绍。

  3. 芯片擦除操作的指令0x07或0x60,当然CS也必须低电平。

  4. 芯片擦除完成后需要等待一段时间(读忙状态),之前有介绍。

  5. 其它与读取ID操作类似。

//芯片擦除,非易失存储器,写数据前应该擦除操作
void W25Q32_Delete(SPI_ObjectTypedef * hSPIx)
{
    W25Q32_Write_Enable(hSPIx);//开使能
    SPI_CS_0(hSPIx);//片选置0
    SPI_SCL_0(hSPIx);
    uSPI_Byte_0(hSPIx,0xC7);//发送指令0xC7或者0x60
    SPI_CS_1(hSPIx);//片选置1,结束
    W25Q32_Wait_End(hSPIx);//擦除完成后需要等待一段时间(读忙状态)
}

7.页写入操作

W25Qxx最大写入单位为页(Page),每页最多写入256Byte,超过256Byte必须另写一页,每16页组成一个扇区(Sector),每16个扇区组成一个块(Block)。W25x32为4MB的容量,共有64个块,1024个扇区,16384个页。地址范围为(0x000000-0x2FFFFF),理论上地址每一位对应一个字节,实际在连续写入大数据的过程中还需考虑页、扇区、块的容量问题。注意写之前必须确保是擦除操作过的,最小的擦除单位不是页而是扇区。

1块=16扇区,1扇区=16页,1页=256字节

  1. 页写操作与擦除操作类似,需要CS低电平、写使能、忙等待。

  2. 页写操作指令为0x02,接下来是24位地址(页的每页的起始地址为:0xXXXX00,页结束地址为0xXXXXFF其中X为16进制任意数)。

  3. 写入地址完成后,再次写入不大于256Byte的数据内容

//页写操作,*buf为内容,addr为地址,pagesize为写入字节数必须小于256个
void W25Q32_Write_Page(SPI_ObjectTypedef * hSPIx,uint8_t * buf, uint32_t addr,uint8_t pagesize)
{
    W25Q32_Write_Enable(hSPIx);//开使能
    SPI_CS_0(hSPIx);//片选置0
    SPI_SCL_0(hSPIx);
    uSPI_Byte_0(hSPIx,0x02);//发送指令0x02
    //24位地址(页的每页的起始地址为:0xXXXX00,页结束地址为0xXXXXFF,其中X为16进制任意数)
    uSPI_Byte_0(hSPIx,addr>>16&0xFF);//二十四位地址中的前八位地址
    uSPI_Byte_0(hSPIx,addr>>8&0xFF); //二十四位地址中的中八位地址
    uSPI_Byte_0(hSPIx,addr&0xFF); //二十四位地址中的中八位地址
    if(pagesize>PAGESIZE)
    {
        pagesize=256;//最多输入256字节
    }
    while(pagesize--)
    {
        uSPI_Byte_0(hSPIx,*buf++);
    }
    SPI_CS_1(hSPIx);//片选置1
    W25Q32_Wait_End(hSPIx);//擦除完成后需要等待一段时间(读忙状态)
}

8.读操作

读操作相对于写操作比较简单,不用考虑页、扇区、块,仅使用24位地址即可读。

  1. 读操作与读ID操作类似,需要CS低电平。

  2. 读操作指令为0x03,接下来是24位地址。

  3. 写入地址完成后直接读出数据内容。

//读操作,*buf为内容,addr为地址,pagesize为要读出的字节数
void W25Q32_Read_Page(SPI_ObjectTypedef * hSPIx, uint8_t *buf,uint32_t addr,uint8_t pagesize)
{
    SPI_CS_0(hSPIx);//片选置0
    SPI_SCL_0(hSPIx);
    uSPI_Byte_0(hSPIx,0x03);//读操作的指令
    //发送24位地址(页的每页的起始地址为:0xXXXX00,页结束地址为0xXXXXFF,其中X为16进制任意数)
    uSPI_Byte_0(hSPIx,(addr>>16)&0xFF);//二十四位地址中的前八位地址
    uSPI_Byte_0(hSPIx,(addr>>8)&0xFF); //二十四位地址中的中八位地址
    uSPI_Byte_0(hSPIx,(addr)&0xFF); //二十四位地址中的中八位地址
    while(pagesize--)
    {
        *buf=uSPI_Byte_0(hSPIx, 0xFF);//读取pagesize个数据
         buf++;
    }
    SPI_CS_1(hSPIx);//片选置1
}
​

9.Demo

从地址00位置开始写入0x01,0x02,0x03,0x04,0x05至W25Q32中并读取其地址00-05的数据通过串口发送给电脑。

void main()。
{
    
    SPI_ObjectTypedef W25Q32_h1;//定义W25Q32的SPI操作句柄
    uint8_t   i;
    uint32_t id;
    uint8_t  write_dat[]={0x01,0x02,0x03,0x04,0x05};//写入内容
    uint8_t  read_dat[15];//读出内容
​
    
    GPIO_Init();//STC15W单片机引脚初始化函数
    
​
    //SCL->P15---MOSI->P14---MISO->P16---CS->P17  //DI-->MOSI DO-->MISO
    vW25Q32_Init(&W25Q32_h1, GPIO_Port_1, GPIO_Port_5, GPIO_Port_1, GPIO_Port_4, GPIO_Port_1, GPIO_Port_6, GPIO_Port_1, GPIO_Port_7);
    
    id=W25Q32_Read_ID(&W25Q32_h1);//读ID
​
    W25Q32_Delete(&W25Q32_h1);//芯片擦除
    
    while(1)
    {
        Delay_ms(500);
        Uart_TX(id/256/256);
        Delay_ms(500);
        Uart_TX(id/256);
        Delay_ms(500);
        Uart_TX(id%256);
        Delay_ms(500);
        W25Q32_Write_Page(&W25Q32_h1, write_dat, 0x000000, 5);//从00位置开始写入
        W25Q32_Read_Page(&W25Q32_h1,read_dat,0x000000,5);//从00位置开始读数据
        for(i=0;i<5;i++)
        {
            Uart_TX(read_dat[i]);//输出所读内容
            Delay_ms(500);
        }
    }
}   
​

所有信息通过串口发送至电脑。

发送的前三个数据是W25Q32的芯片信息,接下来的五个数据就是存入到该存储器的数据。

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值