经常有遇到客户在应用LoRa模组时,SPI调试不通或者有些关于SPI的疑问,下面整理了些关于SPI的相关知识及对LoRa SPI模组的SPI调试进行了简单说明,希望可以帮到一些对LoRa应用不是很熟悉,又需要快速开发的用户。 1.SPI简介 SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是 Motorola首先在其 MC68HCXX 系列处理器上定义的。 SPI 接口主要应用在 EEPROM, FLASH,实时时钟, AD 转换器,还有数字信号处理器和数字信号解码器之间。 SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,LoRa模组采用的也是 SPI 接口。 下面我们看看 SPI 的内部简明图 :
图一 SPI简 SPI 接口一般使用 4 条线通信:
MISO 主设备数据输入,从设备数据输出。
MOSI 主设备数据输出,从设备数据输入。
SCLK 时钟信号,由主设备产生。
CS 从设备片选信号,由主设备控制。
其中,CS是从芯片是否被主芯片选中的控制信号,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),主芯片对此从芯片的操作才有效。这就使在同一条总线上连接多个SPI设备成为可能。通讯是通过数据交换完成的,这里先要知道SPI是串行通讯协议,也就是说数据是一位一位的传输的。这就是SCLK时钟线存在的原因,由SCLK提供时钟脉冲,SDI,SDO则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。因此,至少需要8次时钟信号的改变(上沿和下沿为一次),才能完成8位数据的传输。 SPI 总线四种工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果 CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果 CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI 主模块和与之通信的外设备时钟相位和极性应该一致。 SPI的时钟相位(CPHA)和时钟极性(CPOL)分别可以为0或1,对应组合构成了SPI的4种模式: Mode 0:CPOL=0, CPHA=0 ; 空闲时低电平,第一个跳变沿数据被采样 Mode 1:CPOL=0, CPHA=1; 空闲时低电平,第二个跳变沿数据被采样 Mode 2:CPOL=1, CPHA=0 ; 空闲时高电平,第一个跳变沿数据被采样 Mode 3:CPOL=1, CPHA=1。 空闲时高电平,第二个跳变沿数据被采样 2.LoRa模块的SPI LoRa模块SPI接口通过与Motorola/Freescale术语中的CPOL=0和CPHA=0相对应的同步全双工协议来访问配置寄存器。只实现从机端。 LoRa模块提供了三种对寄存器的访问模式: 单次(SINGLE)访问:一个地址字节后面跟着一个数据字节被发送用于写访问,而一个地址字节被发送并且一个读字节被接收用于读访问。NSS引脚在帧开始时变低,在数据字节之后变高。 突发(BURST)访问:地址字节后面跟着几个数据字节。地址在内部自动递增在每个数据字节之间。此模式可用于读和写访问。NSS引脚在帧的开始,并在每个字节之间保持低位。只有在最后一次字节传输之后,它才会变高。 FIFO访问:如果地址字节对应于FIFO的地址,那么后续的数据字节将处理FIFO。地址不会自动递增,而是被存储,并且不需要在每个数据字节之间发送。NSS管脚在帧开始时变低,在每个字节之间保持低位。只有在最后一次字节传输之后,它才会变高 下图显示了LoRa模块对寄存器的典型SPI单次访问:
图二 LoRa模块对寄存器的典型SPI单次访问
MOSI由SCK的下降沿的主电路产生,并由SCK的上升沿的从电路(即SPI接口)采样。MISO是由SCK下降沿的从机生成的。传输总是由NSS管脚变低开始。当NSS较高时,MISO是高阻抗的。 第一个字节是地址字节。它包括: 一个wnr位,用于写访问为1,用于读访问为0; 然后7位地址,MSB优先; 第二个字节是数据字节,在写访问的情况下由主机通过MOSI发送,在读访问的情况下由主机通过MISO接收。数据字节首先由MSB发送。 处理字节可以在MOSI(用于写访问)上发送,或者在MISO(用于读访问)上接收,而无需上升的NSS边缘并重新发送地址。 在FIFO模式下,如果地址是FIFO地址,那么字节将在FIFO地址被写入/读取。在Burst模式下,如果地址不是FIFO地址,那么对于接收到的每个新字节,它自动递增。 当NSS变高时,帧结束。下一帧必须以地址字节开始。因此,单次访问模式是FIFO/BURST模式的特殊情况,只传输了一个数据字节。
3.实战应用 (1)MCU资源确认,正确连接电路 结合MCU资源,确认下来是使用硬件上的SPI还是需要通过软件模拟来实现。电路连接参考如下:
图三 LoRa模组连接参考电路图 (2)确定LoRa的SPI工作模式 LoRa作为一个从机,其时钟相位和极性分别为CPOL=0, CPHA=0,即空闲时为低电平,第一个跳变沿数据被采样。 (3)下面分别介绍LoRa的SPI单次访问寄存器的软件模拟实现方式和硬件实现方式 LoRa的SPI单次访问寄存器的软件实现: 1、通过软件模拟,我们需先分配好各IO的方向及开始电平;因为CPOL=0, CPHA=0 ,即时钟空闲时为低电平,第一个跳变沿数据被采样(即在这里就为上升沿采样),且传输是由NSS管脚变低开始,当NSS较高时,MISO是高阻抗的。所以配置应该为:MOSI需要配置成输出高、MISO需要配置成输入、SCK需要配置成输出低、NSS需要配置成输出高;如下代码实现: - void SX1276SPISetup(void)
- {
- LSD_SPI_SIMO_PORT_SEL=0 ;
- LSD_SPI_SIMO_PORT_DIR=1 ;
- LSD_SPI_SIMO_PORT_OUT=1;
-
- LSD_SPI_SCK_PORT_SEL=0;
- LSD_SPI_SCK_PORT_DIR=1;
- LSD_SPI_SCK_PORT_OUT=0;
-
- LSD_SPI_SOMI_PORT_SEL=0;
- LSD_SPI_SOMI_PORT_DIR=0;
- LSD_SPI_SOMI_PORT_OUT=0;
-
- LSD_SPI_NSS_PORT_SEL=0 ;
- LSD_SPI_NSS_PORT_DIR=1;
- LSD_SPI_NSS_PORT_OUT=1;
- return;
- }
复制代码2、接下来是根据时钟相位和极性来模拟SPI数据存取; 实现代码如下: - static unsigned char LSD_RF_SpiInOut (unsigned char mosi)
- {
- unsigned char i;
- unsigned char onebyte;
- onebyte = mosi;
- for(i = 8; i > 0; i--)
- {
- __no_operation();
- if(onebyte & 0x80)
- {
- LSD_SPI_SIMO_OUT(1);
- }
- else
- {
- LSD_SPI_SIMO_OUT(0);
- }
- __no_operation();
- LSD_SPI_SCK_OUT(1);
- onebyte <<= 1; // next bit
- if(LSD_SPI_SOMI_IN())
- {
- onebyte++; // 1 found on MISO
- }
- __no_operation();
- LSD_SPI_SCK_OUT(0);
- }
- return onebyte; // mosi now becomes the value of miso
- }
复制代码 LoRa的SPI单次访问寄存器的硬件实现: 1)先配置好SPI相关IO方向及电平,主要是MISO、MOSI、SCK、NSS引脚 实现代码如下(STM32):
- <p>void SX127X_SPIGPIO_Init()
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- /* Configure the SX126X_NSS pin */
- GPIO_InitStruct.Pin = GPIO_PIN_15;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
- /* SPI SCK GPIO pin configuration */
- GPIO_InitStruct.Pin = GPIO_PIN_10;
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
- GPIO_InitStruct.Pull = GPIO_PULLUP;
- GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
- GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
- HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
- /* SPI MISO GPIO pin configuration */
- GPIO_InitStruct.Pin = GPIO_PIN_11;
- GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
- HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
- /* SPI MoSi GPIO pin configuration */
- GPIO_InitStruct.Pin = GPIO_PIN_12;
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
- GPIO_InitStruct.Pull = GPIO_PULLUP;
- GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
- GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
- HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
- }</p>
复制代码2)根据MCU及LoRa的SPI工作模式配置好SPI功能 实现代码如下(STM32):
- void SX127X_SPI_Init()
- {
- __HAL_RCC_GPIOA_CLK_ENABLE();//PORTAʱÖÓʹÄÜ
- __HAL_RCC_GPIOC_CLK_ENABLE();//PORTCʱÖÓʹÄÜ
- __HAL_RCC_SPI3_CLK_ENABLE();//SPI2ʱÖÓʹÄÜ
- SX127X_SPIGPIO_Init();
- SPI3_InitStruct.Instance = SPI3;
- SPI3_InitStruct.Init.Mode = SPI_MODE_MASTER;//SPI模式:主机模式
- SPI3_InitStruct.Init.Direction = SPI_DIRECTION_2LINES;//两线全双工
- SPI3_InitStruct.Init.DataSize = SPI_DATASIZE_8BIT;//数据宽度:8位
- SPI3_InitStruct.Init.CLKPolarity = SPI_POLARITY_LOW; //´®ÐÐͬ²½×ÖʱÖÓ¿ØÖÆΪµÍËÙʱÖÓ
- SPI3_InitStruct.Init.CLKPhase = SPI_PHASE_1EDGE; //CPOL=0;CPHA=0 模式
- SPI3_InitStruct.Init.NSS = SPI_NSS_SOFT;//NSS由软件控制
- SPI3_InitStruct.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
- SPI3_InitStruct.Init.FirstBit = SPI_FIRSTBIT_MSB;//数据从MSB开始
- SPI3_InitStruct.Init.TIMode = SPI_TIMODE_DISABLE;//SPI Motorola mode
- SPI3_InitStruct.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
- SPI3_InitStruct.Init.CRCPolynomial = 7;
- SPI3_InitStruct.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
- SPI3_InitStruct.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
- if (HAL_SPI_Init(&SPI3_InitStruct) != HAL_OK)
- {
- while(1);
- }
- __HAL_SPI_ENABLE(&SPI3_InitStruct);
- }
复制代码(4)LoRa 寄存器访问 如下向寄存器中写和读操作的函数代码,下面代码是从寄存器中读取数据的函数代码,从中可以看出,在操作之前,都将使能端NSS拉低了,并且需要主要写写地址时,最高位为1,读时为0,即SX127X_ReadWriteByte(addr | 0x80); 和 SX127X_ReadWriteByte(addr & 0x7F); - void SX127X_WriteBuffer( uint8_t addr, uint8_t *buffer, uint8_t size )
- {
- uint8_t i;
- SX127X_NSS_OUTPUT(GPIO_PIN_RESET);
- SX127X_ReadWriteByte(addr | 0x80);
- for( i = 0; i < size; i++ )
- {
- SX127X_ReadWriteByte(buffer[i]);
- }
- SX127X_NSS_OUTPUT(GPIO_PIN_SET);
- }
复制代码- void SX127X_ReadBuffer( uint8_t addr, uint8_t *buffer, uint8_t size )
- {
- uint8_t i;
- SX127X_NSS_OUTPUT(GPIO_PIN_RESET);
- SX127X_ReadWriteByte(addr & 0x7F);
- for( i = 0; i < size; i++ )
- {
- buffer[i] = SX127X_ReadWriteByte(0x00);
- }
- SX127X_NSS_OUTPUT(GPIO_PIN_SET);
- }
复制代码
为了验证SPI是否通过或者,避免MCU异常不工作,代码中会去选用LoRa的一个在应用中没用到的寄存器去验证SPI,如下,选用0x24寄存器去读写0x91进行验证; - //SPI 验证
- uint8_t test = 0;
- SX127X_Write( REG_LR_HOPPERIOD, 0x91 );
- SX127X_Read( REG_LR_HOPPERIOD, &test);
- if(test != 0x91)
- return SPI_READCHECK_WRONG;
复制代码图四和图五是用逻辑分析仪抓取出来的实验结果。图四是向地址为0x24的寄存器中写入数据0x91的过程,可以看到,写入地址为0x80|0x24,MOSI输出数据0xX91;图五是从地址为0x24的寄存器中读取数据的过程,读取数据也是0x91,读取数据与写入数据相同,实验成功。
图四
图五 最后附上LoRa模组开发资料及例程代码 |