SPI简介STM32内部集成了硬件SPI收发电路可以由硬件自动执行时钟生成、数据收发等功能减轻CPU的负担可配置8位/16位数据帧、高位先行/低位先行时钟频率 fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)SPI的时钟频率都是由fPLC分频得来的支持多主机模型、主或从操作可精简为半双工/单工通信支持DMA兼容I2S协议STM32F103C8T6 硬件SPI资源SPI1、SPI2SPI1外设在APB2上 72MHzSPI2外设在APB1上 36MHz0全双工两根线一根发一根收半双工一根线分时收发单工一根线发。或者一根线收。I2S是一种音频传输协议数字音频信号传输的专用协议。举例一块主控芯片里面存放了一首音乐的数据这个数据就是一个点一个点的电压数值。是数字信号。如果我们要把这个音乐播放出来.就要外挂一个音频解码器。实际上就是一个DAC数模转换器。把数字信号转换成模拟信号。然后输出到扬声器把音乐播放出来。数据发送到DAC的线路怎么发就是I2S决定的了。SPI框图移位寄存器最右边的最低位移出通过MOSI发出MISO接受数据后送给移位寄存器的最左边高位。图中的是低位先行。LSBFIRST可以控制是低位先行还是高位先行。给1高位给0低位图中红色框图内对MOSI和MISO的引线做了个交叉处理。是为了处理主从模式引脚变换的。SPI外设可以做主机也可以做从机。主机MOSI输出MISO输入。从机MOSI输入MISO输出图中的接受寄存器和发送寄存器在物理层面上是两个独立的寄存器但是他们共用了同一块地址。在软件上就是一个寄存器。当执行写操作时使用的发送寄存器。当执行读操作时。使用的是接收寄存器。数据从地址和数据总线移动到发送寄存器若移位寄存器没有数值就转移到移位寄存器并且置TEX为1说明发送寄存器空快写入数据随后移位寄存器每次来一个时钟移位寄存器就会移出一位数据。MISO会接受一个数据当数据交换完毕时数据会从移位寄存器到接受寄存器此时会置标志位RXNE为1。接收寄存器非空提醒尽快读出数据如果读取RDR数据不及时会导致数据被覆盖。波特率发生器内部就是一个分频器输入时钟是PCLK72M或36M。经过分频后输出到SCK与移位寄存器同步。每产生一个时钟周期移入移出一个bit。CR1寄存器的BR0, BR1, BR2控制分频系数SPI_CR1寄存器移位示意图基本结构传输方式本文代码是按照非连续传输发送写的所以只讲这一个等待TEX 1发送寄存器为空将数据写进TDR发送寄存器MOSI会自动的将数据一位一位的发出去。非连续传输没必要把下一个数据放进TDR中。发送的同时MOSI还会的同步的接收。等待RXNE 1接收寄存器有数据是快来取SPI初始化开启GPIO跟SPI的时钟初始化GPIO其中SCK和MOSI是硬件外设控制的输出信号。配置为复用推挽输出。MISO是硬件控制的输入信号。配置为上拉输入。SS是软件控制的输出信号配置为通用推挽输出SPI结构体参数开关控制SPI_Cmd()SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Mode SPI_Mode_Master; //选择从机还是主机 SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; //SPI 通信的引脚模式和数据传输方向 SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; //配置数据帧是8位还是16位 SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; //配置高位先行 SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_128;//波特率预分频器 SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; //时钟极性,模式0对应Low SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; //哪一个边沿开始采样 SPI_InitStructure.SPI_NSS SPI_NSS_Soft; //选择是软件的还是硬件的我们用GPIO模拟用软件 SPI_InitStructure.SPI_CRCPolynomial 7; //CRC校验 SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE);SPI_Direction参数SPI相关的库函数//将数据写进TDR数据发送寄存器 void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data); //读取RDR数据寄存器里面的内容 uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx); //配置数据帧是8位还是16位 void SPI_DataSizeConfig(SPI_TypeDef* SPIx, uint16_t SPI_DataSize);完整代码#include stm32f10x.h // Device header void MySPI_W_SS(uint8_t BitValue) //GPIO软件控制SS { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); } void MySPI_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; //SS从机选择引脚用软件模拟就还是推挽输出 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; //SCK跟MOSI为主机的输出用推挽输出 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; //MISO为输入引脚。用上拉输入 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Mode SPI_Mode_Master; //选择从机还是主机 SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; //SPI 通信的引脚模式和数据传输方向 SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; //配置数据帧是8位还是16位 SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; //配置高位先行 SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_128;//波特率预分频器 SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; //时钟极性,模式0对应Low SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; //哪一个边沿开始采样 SPI_InitStructure.SPI_NSS SPI_NSS_Soft; //选择是软件的还是硬件的我们用GPIO模拟用软件 SPI_InitStructure.SPI_CRCPolynomial 7; //CRC校验 SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); MySPI_W_SS(1); //默认给SPI输出高电平 } /* * SPI三个基本时序 * 1.起始信号 * 2.终止信号 * 3.交换一个字节 */ //起始信号 void MySPI_Start(void) { //初始化时已经默认SS为高电平了 MySPI_W_SS(0); } //终止信号 void MySPI_Stop(void) { //此时SS已经是低电平 MySPI_W_SS(1); } /* 交换一个字节.参数ByteSend是发送的字节 * 此处使用的是SPI的模式0 * 先移出数据第一个上升沿移入 */ uint8_t MySPI_SwapByte(uint8_t ByteSend) { while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) ! SET ); //等待TEX位置1 SPI_I2S_SendData(SPI1, ByteSend); //发送数据至TDR发送寄存器 while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) ! SET ); //等待RXNE位置1 return SPI_I2S_ReceiveData(SPI1); //返回RDR里面的内容 }补充硬件会自动的给SCK置高低电平。而软件需要手动的置高低电平。硬件会自动地把数据一个一个取出来软件需要手动判断MISO对应GPIO的高低电平。硬件必须先发送再接收。因为只有先发送才会产生时序有了时序还会接收这里的TEX和RXNE可以由软件清零也可以不用管。数据手册描述当TEX 1 时。写入TDR会自动清除TEX标志位。当RXNE 1 时取出RDR会自动清除RXNE标志位