简介

软件SPI灵活可变;硬件SPI高性能,可以减轻软件负担
SPI1在APB2 总线,APB2外设时钟频率=72MHZ;SPI2在APB1 总线,APB1总线外设时钟频率
=36MHZ。SPI的时钟就来源于外设时钟分频,可以是2~256分频。
I2S协议是一种音频协议,和SPI很相似。
结构

SPI中接收寄存器RDR 和发送寄存器TDR 在软件里是同一个地址,表现为一个DR 。硬件上分成TDR和RDR;写入数据到TDR中,如果移位寄存器中此时没有数据,TDR中的数据会马上进入移位寄存器进行移位发送,此时会置TXE标志位;
SPI全双工,发送的同时也在接收,发送完毕时接收也完成了,移位寄存器里接收的数据会进入RDR,同时置标志位RXNE ,此时需要尽快读出数据,不然后面的数据进入RDR会导致数据覆盖。
LSBFIRST 位控制高位先行还是低位先行。寄存器里的BR0、BR1、BR2一起控制时钟分频选择。看MOSI,MISO交叉的地方,当芯片作为主机时,数据通过MOSI发送,通过MISO接收;作为从机时就要交叉过来了,数据通过MISO发送,MOSI接收。
流程

数据传输
全双工连续传输
使用全双工连续传输,效率很高,但同时操作也很复杂,程序不好封装。

开始传输时,在TDR写入数据0xf1,这是第一个数据,所以会马上进入移位寄存器,置TXE标志位了,所以就要继续在TDR写入第二个数据0xf2等待进入移位寄存器;0xf1发送完毕同时也就接收到了第一个数据0xa1,置RXNE标志位了,需要尽快读取出来。
之后0xf2进入移位寄存器了,置TXE就要写入发送的第三个数据0xf3到TDR等待移位;0xf2发送完毕同时收到了第二个数据0xa2,置RXNE需要尽快读出数据。随后最后一个数据0xf3开始移位,置TXE标志位但不写入数据了,最后接收第三个数据0xa3。
整体流程是:写第一个数据------>写第二个数据------>收第一个数据------>写第三个数据------>收第二个数据------>收第三个数据。是交错的,所以软件实施不太友好,比较复杂,但配合得好就能实现连续的数据,效率很高。
非连续传输
操作简单易封装,但效率会低一些。是最常用的。

如图是发送数据的过程。开始在TDR写入第一个数据之后会马上进入移位寄存器,TXE置1了,此时和连续传输时的操作不同,连续传输时一旦TXE置1了就马上写入第二个数据,
但在非连续传输这里不会马上写入,而是等待,等RXNE置1即收到了第一个数据,读出数据之后才继续写入第二个数据,第二个数据进入移位寄存器后也不马上写入第三个数据,同样要等读出第二个数据后才写入第三个数据。
整体流程是:写入第一个数据------>收到第一个数据------>写入第二个数据------>收到第二个数据------>写入第三个数据------>收到第三个数据。
这样就不会出现交叉,软件的执行步骤也可以封装起来:等待TXE置1,写入数据,等待RXNE置1,读取数据。循环下去就行了。但由于等待,就会产生间隙,在SPI时钟频率低的时候间隙没什么影响,但在时钟频率很高的时候,间隙影响就比较大了,比如在二分频时频率很高,间隙的时间是一次数据传输所用时间的好几倍,效率大大降低。所以不追求效率时可以使用非连续传输。
非连续传输代码
MYSPI.c文件
cs
#include "stm32f10x.h" // Device header
void MYSPI_W_SS(uint8_t bitvalue)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)bitvalue);//改变SS
}
void MYSPI_init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
GPIO_InitTypeDef GPIO_InitSturcture;
GPIO_InitSturcture.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitSturcture.GPIO_Pin=GPIO_Pin_5|GPIO_Pin_7;//SCK,MOSI输出引脚为复用推挽输出
GPIO_InitSturcture.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitSturcture);
GPIO_InitSturcture.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitSturcture.GPIO_Pin=GPIO_Pin_4;//SS输出引脚为推挽输出,依旧使用GPIO口模拟SS
GPIO_InitSturcture.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitSturcture);
GPIO_InitSturcture.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitSturcture.GPIO_Pin=GPIO_Pin_6;//MISO输出引脚为上拉输入
GPIO_InitSturcture.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitSturcture);
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;//分频
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_InitStructure.SPI_NSS=SPI_NSS_Soft;//软件模拟SS
SPI_Init(SPI1,&SPI_InitStructure);
SPI_Cmd(SPI1,ENABLE);//使能
MYSPI_W_SS(1);//SS默认高电平
}
void MYSPI_start(void)//开始
{
MYSPI_W_SS(0);
}
void MYSPI_stop(void)//结束
{
MYSPI_W_SS(1);
}
uint8_t MYSPI_byte(uint8_t byte)//交换一个字节
{
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET);//等待标志位TXE置1,好写入数据
SPI_I2S_SendData(SPI1,byte);//写入数据
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET);//等标志位RXNE置1,好读出数据
return SPI_I2S_ReceiveData(SPI1);//读出数据
}
其余和软件SPI代码一样。