STM32-SPI

图片均来自江科大STM32教学PPT

SPI

简介

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

硬件电路

  • 所有SPI设备的SCK、MOSI、MISO分别连在一起
  • 主机另外引出多条SS控制线,分别接到各从机的SS引脚
  • 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入

在这里插入图片描述

移位示意图

波特率发生器时钟上升沿,所有移位寄存器向左移动一位,移除去的位放到引脚上(输出数据寄存器);波特率发射器时钟下降沿,引脚上的位,采样输入到移位寄存器的最低位。

在这里插入图片描述

上升沿数据移出
MOSI数据是1(高电平),MISO数据是0(低电平)

在这里插入图片描述

下降沿数据移入
MOSI的数据会被采样输入到从机最低位;MISO的数据会被采样输入到主机最低位。

在这里插入图片描述

八个时钟周期过后
在这里插入图片描述

SPI的数据收发都是基于字节交换。

  • 发送同时接收
  • 只发送,但也会接收到数据,只是不管接收到的数据。
  • 只接收,可以随便发送一个数据(一般发送0x00或者0xFF)和从机交换。

SPI基本时序单元

起止/终止

  • 起始条件:SS从高电平切换到低电平
  • 终止条件:SS从低电平切换到高电平
  • 从机被选中,SS始终保持低电平

在这里插入图片描述

数据交换

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

SS下降沿,就要立马触发数据移出,SCK第一个上升沿才可以移入数据。

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

SPI时序

IIC:寄存器+读写数据
SPI:指令码+读写数据

  • 发送指令
  • 向SS指定的设备,发送指令(0x06)
    在这里插入图片描述

指定地址写

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

在这里插入图片描述

指定地址读

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

在这里插入图片描述

SPI软件模拟代码示例

#include "stm32f10x.h"                  // Device header

void MySPI_W_SS(uint8_t BitValu)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValu);
}

void MySPI_W_SCK(uint8_t BitValu)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValu);
}

void MySPI_W_MOSI(uint8_t BitValu)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValu);
}

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;
    // SCK MOSI SS 为推挽输出
    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);
    // MISO 为上拉输入
    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);
}

// SPI 起始信号
void MySPI_Start(void)
{
    MySPI_W_SS(0);
}

// SPI 终止信号
void MySPI_Stop(void)
{
    MySPI_W_SS(1);
}

//	SPI模式0 交换数据
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
    uint8_t i, ByteReceive = 0x00;

    for(i = 0; i < 8; i++)
    {
        // 发送字节的最高位
        MySPI_W_MOSI(ByteSend & (0x80 >> i));
        MySPI_W_SCK(1); // 上升沿时钟

        // 接收数据线的状态,如果为1,则设置接收字节的对应位为1
        if(MySPI_R_MISO() == 1)
        {
            ByteReceive |= (0x80 >> i);
        }
        MySPI_W_SCK(0); // 下降沿时钟
    }
    return ByteReceive; // 返回接收到的字节
}

/*
第二种写法
// SPI 交换数据
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
    uint8_t i, ByteReceive = 0x00;

    for(i = 0; i < 8; i++)
    {
        // 将发送字节的最高位发送到MOSI线
        MySPI_W_MOSI(ByteSend & 0x80);

        // 将发送字节左移一位,准备发送下一位
        ByteSend <<= 1;

        // 上升沿时钟,将数据发送到从机并等待接收
        MySPI_W_SCK(1);

        // 检查MISO线的状态,如果为高电平(1),则设置发送字节的最低位为1
        if(MySPI_R_MISO() == 1)
        {
            ByteSend |= 0x01;
        }

        // 下降沿时钟,完成数据的接收
        MySPI_W_SCK(0);
    }

    // 返回接收到的字节
    return ByteSend;
}
*/

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,移位寄存器如果为空,数据就转到移位寄存器,置TXE=1,表示TDR为空,就可以把后面的数据转到TDR。
  • 一旦数据移出完成,就代表数据移入完成(数据交换完成),然后数据被整体移入到RDR,置RXNE=1,表示RDR非空,就需要把数据从RDR尽快读出来。

在这里插入图片描述

SPI基本结构

在这里插入图片描述

主模式全双工连续传输

在这里插入图片描述

非连续传输

  1. 等待RXE=1
  2. 写入发送数据至TDR
  3. 等待RXNE=1
  4. 读取RDR接收数据

在这里插入图片描述

软件/硬件波形对比

在这里插入图片描述

硬件SPI代码示例

#include "stm32f10x.h"                  // Device header

void MySPI_W_SS(uint8_t BitValu)
{
    GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValu);
}


void MySPI_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    // SS 为推挽输出
    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);

    //	SCK MOSI 复用推挽输出
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5|GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);

    //	MISO 上拉输入
    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_InitTypeDef SPI_InitStruct;
    SPI_InitStruct.SPI_Mode = SPI_Mode_Master;  // SPI模式为主机模式
    SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  // 数据传输为全双工模式
    SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;  // 数据大小为8位
    SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;  // 数据传输的第一位为最高位
    SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;  // 时钟分频为128,即时钟频率除以128得到SPI时钟
    SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;  // 时钟极性为低电平时空闲
    SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;  // 时钟相位为第一个边沿采样
    SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;  // 片选信号由软件控制
    SPI_InitStruct.SPI_CRCPolynomial = 7;  // CRC多项式为7
    SPI_Init(SPI1, &SPI_InitStruct);  // 初始化SPI1外设

    SPI_Cmd(SPI1, ENABLE);  // 使能SPI1外设

    //	初始化默认不选中从机
    MySPI_W_SS(1);
}

// SPI 起始信号
void MySPI_Start(void)
{
    MySPI_W_SS(0);
}

// SPI 终止信号
void MySPI_Stop(void)
{
    MySPI_W_SS(1);
}

//	SPI模式0 交换数据
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
    // 等待 SPI 发送缓冲区为空,以确保可以发送数据
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);

    // 发送数据 顺带清除标志位
    SPI_I2S_SendData(SPI1, ByteSend);

    // 等待接收缓冲区中有数据,以确保可以接收数据
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);

    // 从 SPI 接收数据 顺带清除标志位
    return SPI_I2S_ReceiveData(SPI1);
}

)
{
    // 等待 SPI 发送缓冲区为空,以确保可以发送数据
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);

    // 发送数据 顺带清除标志位
    SPI_I2S_SendData(SPI1, ByteSend);

    // 等待接收缓冲区中有数据,以确保可以接收数据
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);

    // 从 SPI 接收数据 顺带清除标志位
    return SPI_I2S_ReceiveData(SPI1);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值