SPI传输方式
采用同步方式(Synchronous)传输数据
Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的。
typedef struct
{
uint16_t SPI_Direction; // 设置SPI 的通信方式,可以选择为半双工,全双工,以及串行发和串行收方式
uint16_t SPI_Mode; // 设置SPI 的主从模式
uint16_t SPI_DataSize; // 为8 位还是16 位帧格式选择项
uint16_t SPI_CPOL; // 设置时钟极性
uint16_t SPI_CPHA; // 设置时钟相位
uint16_t SPI_NSS; //设置NSS 信号由硬件(NSS管脚)还是软件控制
uint16_t SPI_BaudRatePrescaler; //设置SPI 波特率预分频值
uint16_t SPI_FirstBit; //设置数据传输顺序是MSB 位在前还是LSB 位在前
uint16_t SPI_CRCPolynomial; //设置CRC 校验多项式,提高通信可靠性,大于1 即可
}SPI_InitTypeDef;
void SPI1_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PA5/6/7复用推挽输出 //主从模式决定数据方向
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA
GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7); //PA5/6/7上拉
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步时钟的空闲状态为低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //串行同步时钟的第一个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; //定义波特率预分频的值:波特率预分频值为2
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI1, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,ENABLE);
SPI_Cmd(SPI1, ENABLE); //使能SPI外设
}
void SPI2_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15上拉
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步时钟的空闲状态为低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //串行同步时钟的第1个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; //定义波特率预分频的值:波特率预分频值为2
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,ENABLE);
SPI_Cmd(SPI2, ENABLE); //使能SPI外设
}
void SPI1_IRQHandler(void)
{
if(SPI_I2S_GetITStatus(SPI1,SPI_I2S_IT_RXNE) !=RESET)//中断标志位对应中断的Get
{
Buffer1_Rx[RxCNT1++] = SPI_I2S_ReceiveData(SPI1);//清除标志位,RXNE=0
if(RxCNT1 == buffer_size)
{
SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,DISABLE);
}
}
}
void SPI2_IRQHandler(void)
{
if(SPI_I2S_GetITStatus(SPI2,SPI_I2S_IT_RXNE) !=RESET)//中断标志位对应中断的Get
{
Buffer2_Rx[RxCNT2++] = SPI_I2S_ReceiveData(SPI2);//清除标志位,RXNE=0
if(RxCNT2 == buffer_size)
{
SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,DISABLE);
}
}
}
模拟时序
很典型的一个写8bit数据的函数,入口数据dat,首先选中LCD,进入循环,对dat拆出高位bit分析是1还是0,再决定MOSI输出的是1还是0,并且这个过程是在一个时钟脉冲内完成的。一次循环结束后,dat左移一位,将低一位的bit推向高位,为下一次循环做好准备。在8次循环过后,就完成了一个8bit数据的发送。其实LCD的使用中,SPI相关的只有以下函数,其他的都是在此之上进行的扩展。(没有明显区分采样时刻是奇数还是偶数边沿)
void LCD_Writ_Bus(u8 dat)
{
u8 i;
LCD_CS_Clr();
for(i=0;i<8;i++)
{
LCD_SCLK_Clr();
if(dat&0x80)
{
LCD_MOSI_1();
}
else
{
LCD_MOSI_0();
}
LCD_SCLK_Set();
dat<<=1;
}
LCD_CS_Set();
}
在SPI通信中,MISO信号线不需要单独设置为输入管脚。这是因为在主模式下,MISO信号线的输入模式是由SPI控制器自动配置的。当主设备向从设备发送数据时,从设备会将数据通过MISO信号线返回给主设备,此时SPI控制器会自动将MISO信号线的输入模式设置为输入模式,以读取从设备返回的数据。而在从模式下,MISO信号线的输入模式也是由SPI控制器自动配置的,从设备只需要将数据通过MISO信号线发送给主设备即可。
#define SPI_Direction_2Lines_FullDuplex ((uint16_t)0x0000)
#define SPI_Direction_2Lines_RxOnly ((uint16_t)0x0400)
#define SPI_Direction_1Line_Rx ((uint16_t)0x8000)
#define SPI_Direction_1Line_Tx ((uint16_t)0xC000)
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)
{
assert_param(IS_SPI_ALL_PERIPH(SPIx));/*检查参数*/
SPIx->DR = Data;/*在DR寄存器中写入要发送的数据*/
}
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)
{
assert_param(IS_SPI_ALL_PERIPH(SPIx));/*检查参数*/
return SPIx->DR;/*返回DR寄存器中的数据*/
}
#define SPI_I2S_FLAG_RXNE ((uint16_t)0x0001)
#define SPI_I2S_FLAG_TXE ((uint16_t)0x0002)
#define SPI_I2S_IT_TXE ((uint8_t)0x71)
#define SPI_I2S_IT_RXNE ((uint8_t)0x60)
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
while (TxCNT2 < buffer_size)
{
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET); //等待发完后置1(上一次)
SPI_I2S_SendData(SPI2,Buffer2_Tx[TxCNT2++]);//无时钟SCK输出,TDR2—>TSR2---x->MOSI(PB14)
//while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);//若为主设备时该句可放后面
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); //等待SPI1发送完置1
SPI_I2S_SendData(SPI1,Buffer1_Tx[TxCNT1++]);//有时钟SCK输出,TDR1—>TSR1---x->MOSI(PA7)
//与此同时,TSR2->MOSI(PB14)
SPI_I2S_ITConfig(SPI1,SPI_I2S_IT_RXNE,ENABLE);
SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,ENABLE);
}
查询时
while (RxCNT2 < buffer_size)
{
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET); //等待发完后置1(上一次)
SPI_I2S_SendData(SPI2,Buffer2_Tx[TxCNT2++]);//无时钟SCK输出,TDR2—>TSR2---x->MOSI(PB14)
//while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);//若为主设备时该句可放后面
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); //等待SPI1发送完置1
SPI_I2S_SendData(SPI1,Buffer1_Tx[TxCNT1++]);//有时钟SCK输出,TDR1—>TSR1---x->MOSI(PA7)
//与此同时,TSR2->MOSI(PB14)
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); //等待SPI1接受完置1
Buffer1_Rx[RxCNT1++] = SPI_I2S_ReceiveData(SPI1);//清除标志位,RXNE=0
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET); //等待SPI2接收完置1
Buffer2_Rx[RxCNT2++] = SPI_I2S_ReceiveData(SPI2);//清除标志位,RXNE=0
}
/**********配置RCC时钟,打开对应端口的时钟**********/
RCC_APB2PeriphClockCmd( RCC_APB2Periph_SPI1, ENABLE );//SPI2时钟使能
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );//SPI2时钟使能
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能
/***************NVIC中断优先级配置程序********************/
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/*调用时记得选择分组,要么在main里加,要么在这里加,配置一次即可*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
/**************用哪个加哪个,加在后面就行***********************/
NVIC_InitStructure.NVIC_IRQChannel = SPI1_IRQn; //对应的中断向量通道//在stm32f10x.h中可以找到对应的外部通道名字
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;//抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器
}