硬件SPI(STM32笔记)
SPI简介:
STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
可配置8位/16位数据帧、高位先行/低位先行
时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256),SPI的时钟频率都是由fPLC分频得来的
支持多主机模型、主或从操作
可精简为半双工/单工通信
支持DMA
兼容I2S协议
STM32F103C8T6 硬件SPI资源:SPI1、SPI2
SPI1外设在APB2上 72MHz,SPI2外设在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数据不及时,会导致数据被覆盖。
波特率发生器内部就是一个分频器,输入时钟是PCLK,72M或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标志位
