`
本帖最后由 MMCU5721167 于 2019-1-25 09:41 编辑
来源 灵动微电MMCU
上一章节中已经教大家如何使用I2C控制一个OLED模块,本章节将与大家一起配置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-01BA03.html
图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("
- PROM Data:
- ");
- for (i =0;i<8;i++)
- printf("PROMData%d = %4x
- ",i,PROMData[i]);
- printf("
- ReadTemperature = %x
- ",D2_Temp);
- printf("ReadPressure = %x
- ",D1_Pres);
- printf("CalculateTemperature = %f
- ",Temperature/100);
- printf("CalculatePressure = %f
- ",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通信,进一步熟悉芯片的使用。
灵动微电子股份有限公司(股票代码:833448,股票简称:灵动微电)是国内专注于MCU产品与MCU应用方案的领先供应商,是中国工业及信息化部和上海市信息化办公室认定的集成电路设计企业,同时也是上海市认定的高新技术企业。自2011年3月成立至今,灵动微电子已经成功完成数百余MCU产品的设计及推广,灵动微电子目前已批量供货的基于ARMCortex-M0及Cortex-M3 内核的MCU产品包括:针对通用高性能市场的MM32F系列,针对超低功耗及安全应用的MM32L系列,具有多种无线连接功能的MM32W系列,电机驱动及控制专用的MM32SPIN系列,以及针对超小尺寸及超高集成度的MM32P系列等,以满足客户及市场多领域、多层次的丰富应用场景需求。
灵动微电子立足本土,洞悉市场,贴近客户,以为客户提供“保姆式”的全方位支持为特色,坚持“专业、可靠、便捷、高效”的服务理念,贯彻差异最大化,成本最优化的经营策略,不断强化自身生态价值,维护良好产品品牌。公司在销售初期就与客户充分接触,为客户提供产品整体解决方案,从产品功能定义、市场竞争力分析到算法整合、软件驱动、应用例程等都深入参与,为客户提供精准的市场分析和全面的应用方案,帮助客户把握好成功的每一个重要环节。
联系我们
更多信息请访问:www.mm32mcu.com,微信公众号请搜索“灵动微电MMCU”,QQ技术讨论群:294016370