MM32SPIN2x 电机专用MCU功能特色 —— SPI功能

demi的头像
demi 发布于:周五, 10/25/2019 - 14:02 ,关键词:

本章节将与大家一起配置SPI使用GY-63气压传感器模块。

SPI介绍

SPI(Serial Peripheral Interface, 同步外设接口)是由摩托罗拉公司开发的全双工同步串行总线,该总线大量用在与EEPROM、ADC、FRAM和显示驱动器之类的慢速外设器件通信。

SPI 通信原理比 I2C要简单,它主要是主从方式通信,这种模式通常只有一个主机和一个或者多个从机,如图1,标准的 SPI 是 4 根线,分别是 SSEL(片选,也写作 SCS)、SCLK(时钟,也写作 SCK)、MOSI(主机输出从机输入Master Output/Slave Input)和 MISO(主机输入从机输出 Master Input/Slave Output)。


图1 SPI单主单从应用


图2 SPI时序图

SPI接口有四种不同的数据传输时序,取决于CPOL和CPHL这两位的组合。图2中表现了这四种时序,时序与CPOL、CPHA的关系也可以从图中看出。在通信时,主机和从机必须要配置成相同的时序模式。

CPOL是用来决定SCK时钟信号空闲时的电平,CPOL=0,空闲电平为低电平,CPOL=1时,空闲电平为高电平。CPHA是用来决定采样时刻的,CPHA=0,在每个周期的第一个时钟沿采样,CPHA=1,在每个周期的第二个时钟沿采样。

在SPIN27上的SPI拥有如下特性:
• 完全兼容 Motorola 的 SPI 规格

• 支持各 8 个对应配置数据位(Data size)的发送缓冲器和接收缓冲器
• 支持 DMA 请求
• 在 3 根线上支持全双工同步传输
• 16 位的可编程波特率生成器
• 支持主机模式和从机模式

• 支持一个主机多个从机操作
• 主机模式下SPI的时钟最快可高达36M,从机模式下SPI的时钟最快可高达18M
• 可编程的时钟极性和相位
• 可编程的数据顺序, MSB在前或者LSB在前
• 支持 1 ∼ 32 位的数据位长度同时发送和接收注:除了 8 位数据收发,其余 1 ∼ 32 位数据收发只支持 LSB 模式,不支持 MSB 模式。

下面我们简单的配置一下MM32SPIN27的SPI主机模式:

SPI主机模式配置:

void SPIM_Init(SPI_TypeDef* SPIx,unsigned short spi_baud_div)

{

SPI_InitTypeDef SPI_InitStructure;

GPIO_InitTypeDef  GPIO_InitStructure;

      

if(SPIx==SPI1)

{

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

 

GPIO_PinAFConfig(GPIOA, GPIO_PinSource4,GPIO_AF_0);  

GPIO_PinAFConfig(GPIOA, GPIO_PinSource5,GPIO_AF_0);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource6,GPIO_AF_0); 

GPIO_PinAFConfig(GPIOA, GPIO_PinSource7,GPIO_AF_0);

               

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_4;               //spi1_cs  pa4

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //推挽输出

GPIO_Init(GPIOA, &GPIO_InitStructure);

             

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_5;               //spi1_sck  pa5

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 推免复用输出

GPIO_Init(GPIOA, &GPIO_InitStructure);

      

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_7;               //spi1_mosi  pa7

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 推免复用输出

GPIO_Init(GPIOA, &GPIO_InitStructure);

             

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_6;               //spi1_miso  pa6

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;              //上拉输入  

GPIO_Init(GPIOA, &GPIO_InitStructure);

}

if(SPIx==SPI2)

{

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);

RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

 

GPIO_PinAFConfig(GPIOB, GPIO_PinSource12,GPIO_AF_0);  

GPIO_PinAFConfig(GPIOB, GPIO_PinSource13,GPIO_AF_0);

GPIO_PinAFConfig(GPIOB, GPIO_PinSource14,GPIO_AF_0);  

GPIO_PinAFConfig(GPIOB, GPIO_PinSource15,GPIO_AF_0);

             

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_12;            

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 推免复用输出

GPIO_Init(GPIOB, &GPIO_InitStructure);

             

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_13;             //spi2_sck  pb13

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 推免复用输出

GPIO_Init(GPIOB, &GPIO_InitStructure);

             

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_15;             //spi2_mosi  pb15

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 推免复用输出

GPIO_Init(GPIOB, &GPIO_InitStructure);

                    

GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_14;              //spi2_miso  pb14

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;              //上拉输入  

GPIO_Init(GPIOB, &GPIO_InitStructure);

}

SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

SPI_InitStructure.SPI_DataWidth = SPI_DataWidth_8b;  

SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;              //空闲时钟电平

SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;          //采样时钟沿

SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                  //软件控制片选线

SPI_InitStructure.SPI_BaudRatePrescaler = spi_baud_div;     //时钟分频

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;         

SPI_Init(SPIx, &SPI_InitStructure);

      

SPI_Cmd(SPIx, ENABLE);         

SPI_BiDirectionalLineConfig(SPIx, SPI_Direction_Tx);            //启用发送功能

SPI_BiDirectionalLineConfig(SPIx, SPI_Direction_Rx);           //启用接收功能

}

下面是一些常用操作函数:

void SPIM_CSLow(SPI_TypeDef* SPIx)

{

SPI_CSInternalSelected(SPIx, SPI_CS_BIT0,ENABLE);

}

void SPIM_CSHigh(SPI_TypeDef* SPIx)

{

SPI_CSInternalSelected(SPIx, SPI_CS_BIT0,DISABLE);

}

unsigned int SPIMReadWriteByte(SPI_TypeDef* SPIx,unsigned char tx_data)

{

SPI_SendData(SPIx, tx_data);   

while(!SPI_GetFlagStatus(SPIx, SPI_FLAG_RXAVL));

return SPI_ReceiveData(SPIx);

}

接下来,调用上面的SPIM_Init()函数,就可以初始化SPI主机模式了

按照SPI标准,发送和接收数据的时候,需要先用SPIM_CSLow()或SPIM_CSHigh()设置片选信号,再用SPIMReadWriteByte()函数同时收发数据,完成操作后再设置片选信号为空闲,以此完成一次通信。


图3 数据收发

可以看到,图中,CS输出低电平,开始一次通信,主机发送的数据为0xA2,0x00,0x00,从机发送的数据为0xFE,0xB9,0xB9,CS输出高电平,此次通信结束。

下面,我们以市面上常见的基于MS5611的GY-63气压传感器为例,介绍一个SPIN27的SPI接口应用。

气压传感器应用

MS5611气压传感器是由MEAS(瑞士)推出的一款SPI和I²C总线接口的新一代高分辨率气压传感器,分辨率可达到10cm。MS5611提供了一个精确的24位数字压力值和温度值以及不同的操作模式,可以提高转换速度并优化电流消耗。5.0毫米×3.0毫米×1.0毫米的小尺寸可以集成在移动设备中。

可以从下面的网址获取详细数据手册:

https://www.alldatasheetcn.com/datasheet-pdf/pdf/880802/TEC/MS5611-01BA0...


图4 MS5611和GY-63模块

GY-63程序代码:

u16 PROMData[8]={0};  //实际使用6个u16

uint32_t D1_Pres,D2_Temp; // 存放数字压力和温度

float Pressure; //温度补偿大气压

float dT,Temperature,Temperature2;//实际和参考温度之间的差异,实际温度,中间值

double OFF,SENS;  //实际温度抵消,实际温度灵敏度

float Aux,OFF2,SENS2;  //温度校验值

 

void SPIM_Test(SPI_TypeDef* SPIx)

{

int i =0;

SPIM_Init(SPIx,0x4);//12MHz

      

Reset_GY_63(SPIx);     //复位,0x1E

ReadPROM(SPIx,PROMData);

GetTemperature(SPIx,Convert_D2_OSR_4096); //0x58

GetPressure(SPIx,Convert_D1_OSR_4096);             //0x48

      

printf("\nPROM Data: \r\n");

for (i =0;i<8;i++)

printf("PROMData%d = %4x\r\n",i,PROMData[i]);

printf("\r\nReadTemperature = %x\r\n",D2_Temp);

printf("ReadPressure = %x\r\n",D1_Pres);

printf("CalculateTemperature = %f\r\n",Temperature/100);

printf("CalculatePressure = %f\r\n",Pressure);

}

void SetConvertMode(SPI_TypeDef*SPIx,u8 command)

{

SPIM_CSLow(SPIx);                                                     

SPIMReadWriteByte(SPIx,command);

SPIM_CSHigh(SPIx);

delay_ms(10);              //d1_4096 ≥ 8.22ms

}

u32 ReadADCData(SPI_TypeDef*SPIx)

{

u32 ADCData=0;

SPIM_CSLow(SPIx);                                                     

SPIMReadWriteByte(SPIx,ADC_Read);

ADCData=SPIMReadWriteByte( SPIx,0);

ADCData=ADCData<<8 |SPIMReadWriteByte( SPIx,0);

ADCData=ADCData<<8 |SPIMReadWriteByte( SPIx,0);

SPIM_CSHigh(SPIx);

return ADCData&0xFFFFFF;

}

 

void Reset_GY_63(SPI_TypeDef*SPIx)

{

SPIM_CSLow(SPIx);                                                     

SPIMReadWriteByte(SPIx,GY_63_RESET);

SPIM_CSHigh(SPIx);

delay_ms(5);  //等待复位,≥2.8ms

}

 

void ReadPROM(SPI_TypeDef*SPIx,u16 * PROMData_t)

{

int PROM_Addr = 0;

for (PROM_Addr=0xA0;PROM_Addr<=0xAE;PROM_Addr+=2)

{

SPIM_CSLow(SPIx);

SPIMReadWriteByte(SPIx,PROM_Addr);//PROM

*PROMData_t = SPIMReadWriteByte( SPIx,0);//PROM

*PROMData_t = *PROMData_t<<8|SPIMReadWriteByte( SPIx,0);//PROM

SPIM_CSHigh(SPIx);

PROMData_t++;

}

}

 

void GetTemperature(SPI_TypeDef*SPIx,u16 OSRTemp)

{

SetConvertMode(SPIx,OSRTemp);

D2_Temp= ReadADCData(SPIx);

      

dT=D2_Temp - (((uint32_t)PROMData[5])<<8);

Temperature=2000+dT*((uint32_t)PROMData[6])/8388608;       //算出温度值的100倍

}

void GetPressure(SPI_TypeDef*SPIx,u16 OSRTemp)

{

SetConvertMode(SPIx,OSRTemp);

D1_Pres = ReadADCData(SPIx);

OFF=(uint32_t)(PROMData[2]<<16)+((uint32_t)PROMData[4]*dT)/128.0;

SENS=(uint32_t)(PROMData[1]<<15)+((uint32_t)PROMData[3]*dT)/256.0;

//温度补偿

if(Temperature < 2000)

{

Temperature2 = (dT*dT) / 0x80000000;

Aux = (Temperature-2000)*(Temperature-2000);

OFF2 = 2.5*Aux;

SENS2 = 1.25*Aux;

if(Temperature < -1500)

{

Aux = (Temperature+1500)*(Temperature+1500);

OFF2 = OFF2 + 7*Aux;

SENS2 = SENS + 5.5*Aux;

}

}else  //(Temperature > 2000)

{

Temperature2 = 0;

OFF2 = 0;

SENS2 = 0;

}

Temperature = Temperature - Temperature2;

OFF = OFF - OFF2;

SENS = SENS - SENS2;    

 

Pressure=(D1_Pres*SENS/2097152.0-OFF)/32768.0;

}

int main(void)

{

Uart_ConfigInit(9600);

delay_init();

      

SPIM_Test(SPI1);        

while(1)

{

}

}

程序运行结果:

按照数据手册,连接MCU和模块对应的引脚,模块的PS引脚接地选择SPI模式,编译下载运行程序,在UART输出如下结果:


图5 程序运行结果

从上图,我们可以看到MCU成功读取到模块的PROM和两种模式下ADC的数据,并且通过初步计算得到了温度和气压。

同样,我们也可以通过逻辑分析仪,分析芯片与模块之间的数据交互,进一步了解SPI通信,进一步熟悉芯片的使用。

来源:灵动MM32MCU

围观 407