配合我的上一篇SPI ​​​​​​通信 协议-CSDN博客一起理解更佳,本文后看

       SPI 是由摩托罗拉(Motorola)公司开发的全双工同步串行总线,是 MCU 和外围设备之间进行通信的同步串行端口。主要应用在EEPROM、Flash、RTC、ADC、网络控制器、MCU、DSP以及数字信号解码器之间。SPI 系统可直接与各个厂家生产的多种标准外围器件直接接口,一般使用4 条线:SCK、MISO、MOSI 、SS

STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担

  • 可配置8位/16位数据帧、高位先行/低位先行
  • 时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256) 支持多主机模型、主或从操作
  • 可精简为半双工/单工通信
  • 支持DMA 兼容I2S协议
  • STM32F103C8T6 硬件SPI资源:SPI1(APB2)、SPI2(APB1)

    SPI基本图 

    NSS
    :从设备选择。这是一个可选的引脚,用来选择主
    /
    从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。

    LSBFIRST是低位先行
    。如果是MSBFIRST,那么就是高位先行,

    此时MISO和MOSI需要换,如图的另一种指法。

    SPI基本结构 

    注意:该图是高位先行 

    主模式传输 

    主模式、全双工模式下

    连续传输(BIDIMODE=0并且RXONLY=0)时

    TXE/RXNE/BSY 的变化示意图

     

    本质上来说:第一个字节数据发送,发送时下一个字节就已经到发送缓冲器等待,当第一个字节发送成功,立马发送第二个,第三个字节进入发送缓冲器等待 。

    非连续传输发送(BIDIMODE=0并且RXONLY=0)时

    TXE/BSY变化示意图 

    非连续传输发送来说:第一个字节数据发送,发送成功后,下一个字节才到发送缓冲器,再发送第二个,等发送成功后,第三个字节再进入发送缓冲器。

    代码 

    /**
      * 函    数:SPI初始化
      * 参    数:无
      * 返 回 值:无
      */
    void MySPI_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);	//开启SPI1的时钟
    	
    	/*GPIO初始化*/
    	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);			
    	//将PA4引脚初始化为推挽输出
    	
    	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);				
    	//将PA5和PA7引脚初始化为复用推挽输出
    	
    	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);				
    	//将PA6引脚初始化为上拉输入
    	
    	/*SPI初始化*/
    	SPI_InitTypeDef SPI_InitStructure;					//定义结构体变量
    	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//模式,选择为SPI主模式
    	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    	//方向,选择2线全双工
    	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;	//数据宽度,选择为8位
    	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//先行位,选择高位先行
    	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
    	//波特率分频,选择128分频
    	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;			//SPI极性,选择低极性
    	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;		
        //SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
    	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;			//NSS,选择由软件控制
    	SPI_InitStructure.SPI_CRCPolynomial = 7;			//CRC多项式,暂时用不到,给默认值7
    	SPI_Init(SPI1, &SPI_InitStructure);					
        //将结构体变量交给SPI_Init,配置SPI1
    	
    	/*SPI使能*/
    	SPI_Cmd(SPI1, ENABLE);								//使能SPI1,开始运行
    	
    	/*设置默认电平*/
    	MySPI_W_SS(1);										//SS默认高电平
    }
    SPI起始 
    void MySPI_W_SS(uint8_t BitValue)
    {
    	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);	
    	//根据BitValue,设置SS引脚的电平
    }
    
    /**
      * 函    数:SPI起始
      * 参    数:无
      * 返 回 值:无
      */
    void MySPI_Start(void)
    {
    	MySPI_W_SS(0);				//拉低SS,开始时序
    }
    
    SPI终止 
    /**
      * 函    数:SPI终止
      * 参    数:无
      * 返 回 值:无
      */
    void MySPI_Stop(void)
    {
    	MySPI_W_SS(1);				//拉高SS,终止时序
    }
    
    交换一个字节 
    /**
      * 函    数:SPI交换传输一个字节,使用SPI模式0
      * 参    数:ByteSend 要发送的一个字节
      * 返 回 值:接收的一个字节
      */
    uint8_t MySPI_SwapByte(uint8_t ByteSend)
    {
    	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);
    	//等待接收数据寄存器非空
    	
    	return SPI_I2S_ReceiveData(SPI1);							
    	//读取接收到的数据并返回
    }
    

    作者:上山的月

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 SPI硬件外设详解

    发表回复