本章节将与大家一起配置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