LoRa移植和驱动开发
好久没有写博文了,这从今还要捡起来,今天发一个最近移植的LoRa文档,粘贴出来供大家参考。里面也有一些参考别人的过程。
现有的驱动移植到STM32F0(STM32Cubemx)上。官网SX12xxx系列驱动sx12xxdrivers-v2.1.0.zip。解压后:

图1‑1 解压后库文件
开发之前先了解几个专业术语:
表 1-1专业数据表
FHSS | 跳频扩频技术 | FIFO | 先进先出队列,这里代表队列寄存器 |
PA | 功率放大器 | LNA | 低噪声放大器 |
SNR | 信噪比 | SF | 扩频因子 |
PLL | 锁相环 | CAD | 信道活动检测 |
CR | 编码率 | BW | 带宽 |
RS | 符号速率 | Preamble | 序头 |
文件的驱动程序接口在src下platform和radio文件夹复制到你自己的工程中,如下图:

图1‑2 LoRa库文件
在IAR工程中新建两个文件目录radio和sx12xxEiger。

在两个文件目录中添加LoRa驱动文件,

图1‑3添加驱动文件

同理在sx12xEiger中添加文件,因为我们使用的是SPI通信协议,所以选择如下:

整体移植如下:

图1‑4 工程中添加库文件
以上图为基础删除和我们芯片无关的文件(我们是sx1278),即删除sx1232和sx1272的文件。

图1‑5保留文件
移植完之后,编译,出现很多错误。需要修改里面的一些配置,因为LoRa是基于STM32F10的库做。而我们是把现有的库移植到STM32Cubemx上,可能有些库中结构体名字、头文件和函数名不一致,需要进行修改。这个自己可以慢慢修改,这里就不说了。直到编译没有错误为止。
1.1 库SPI修改
MCU和LoRa模块使用的是SPI总线方式,参考原理图查看使用那几个我们使用STM32Cubemx实现SPI驱动。然后修改LoRa库中的引脚配置(DIO0和片选线)和SPI库函数操作。

图1‑6 LoRa连接MCU引脚
如上图,在STM32Cubemx配置上图的红色框中的引脚即可,PA7作为片选配置为输出,默认拉高。其它的按SPI的引脚固定引脚配置。

图1‑7 STM32Cubemx自动生成SPI驱动文件
配置完之后,会在user目录下生成一个spi.c文件。在文件下添加读取数据的函数。
片选引脚:
COBOL Code
1 2 3 4 5 6 7 | #define SX12xx_NSS_IOPORT GPIOA #define SX12xx_NSS_PIN GPIO_PIN_7 //片选信号 //NSS = 0 HAL_GPIO_WritePin(SX12xx_NSS_IOPORT, SX12xx_NSS_PIN,GPIO_PIN_RESET); //NSS = 1 HAL_GPIO_WritePin(SX12xx_NSS_IOPORT, SX12xx_NSS_PIN,GPIO_PIN_SET); |
SPI写读操作函数:
COBOLCode
1 2 3 4 5 6 7 8 9 10 11 | uint8_t SpiInOut( uint8_t outData ) { uint8_t pData=0; if( HAL_SPI_TransmitReceive(&hspi1,&outData,&pData,1,0xfff)!= HAL_OK) { return ERROR; } return pData; } |
修改下面函数SX1276WriteBuffer和SX1276ReadBuffer中的片选线如下面代码(改为STM32Cubemx库中操作):
COBOLCode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | void SX1276WriteBuffer( uint8_t addr, uint8_t *buffer, uint8_t size ) { uint8_t i; //NSS = 0; HAL_GPIO_WritePin(SX12xx_NSS_IOPORT, SX12xx_NSS_PIN,GPIO_PIN_RESET); SpiInOut( addr | 0x80 ); for( i = 0; i < size; i++ ) { SpiInOut( buffer[i] ); } //NSS = 1; HAL_GPIO_WritePin(SX12xx_NSS_IOPORT, SX12xx_NSS_PIN,GPIO_PIN_SET); } void SX1276ReadBuffer( uint8_t addr, uint8_t *buffer, uint8_t size ) { uint8_t i; //NSS = 0; HAL_GPIO_WritePin(SX12xx_NSS_IOPORT, SX12xx_NSS_PIN,GPIO_PIN_RESET); SpiInOut( addr & 0x7F ); for( i = 0; i < size; i++ ) { buffer[i] = SpiInOut( 0 ); } //NSS = 1; HAL_GPIO_WritePin(SX12xx_NSS_IOPORT, SX12xx_NSS_PIN,GPIO_PIN_SET); } |
1.2 修改LoRa库参考时间
为什么需要修改这个时间,是因为其库操作中是基于状态机操作,有超时检测的事件,参照TickCounter计数。
COBOLCode
1.3 LED灯
由于LoRa库中的LED和我们的模块的引脚不一致,需要我们根据原理图进行修改。

图1‑8 LED引脚
上面三个引脚连接到底板上发送,接收和网络3个指示灯上,在led.h和led.c中
注销LED相关的初始化,只需更改LED相关端口和引脚的配置。如下配置。
COBOLCode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #define LED_NB 4 // RED #define LED1_PIN GPIO_PIN_1 #define LED1_GPIO_PORT GPIOB // GREEN #define LED2_PIN GPIO_PIN_2 #define LED2_GPIO_PORT GPIOB // DBG1 #define LED3_PIN GPIO_PIN_1 #define LED3_GPIO_PORT GPIOB // DBG2 #define LED4_PIN GPIO_PIN_0 #define LED4_GPIO_PORT GPIOB |
还需更改LED的操作函数LedOn和LedOff。
COBOLCode
1 2 3 4 5 6 7 8 9 10 | //LED是共阳极电路,引脚置地,灯开。 void LedOn( tLed led ) { HAL_GPIO_WritePin(LedPort[led], LedPin[led],GPIO_PIN_RESET ); } void LedOff( tLed led ) { HAL_GPIO_WritePin(LedPort[led], LedPin[led], GPIO_PIN_SET); } |
1.4 添加printf
STM32Cubemx生产的工程本身不可以直接使用printf,需要在工程中添加下面代码即可。使用串口1打印信息。
COBOLCode
1 2 3 4 5 6 | int fputc(int ch, FILE *f) { while((USART1->ISR & 0X40)==0); USART1->TDR = (uint8_t)ch; return ch; } |
1.5 修改loRa配置参数
次处根据LoRa芯片操作,我们使用的是sx1278频段范围在410~525MHz,所以我们配置为434000000。具体配置如下:
COBOLCode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #define LoRa_FREQENCY 434000000 // Default settings tLoRaSettings LoRaSettings = { LoRa_FREQENCY, //870000000, // RFFrequency 20, // Power 9, // SignalBw [0: 7.8kHz, 1: 10.4 kHz, 2: 15.6 kHz, 3: 20.8 kHz, 4: 31.2 kHz, // 5: 41.6 kHz, 6: 62.5 kHz, 7: 125 kHz, 8: 250 kHz, 9: 500 kHz, other: Reserved] 7, // SpreadingFactor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips] 2, // ErrorCoding [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] true, // CrcOn [0: OFF, 1: ON] false, // ImplicitHeaderOn [0: OFF, 1: ON] 1, // RxSingleOn [0: Continuous, 1 Single] 0, // FreqHopOn [0: OFF, 1: ON] 4, // HopPeriod Hops every frequency hopping period symbols 100, // TxPacketTimeout 100, // RxPacketTimeout 128, // PayloadLength (used for implicit header mode) }; |
1.6 程序调试
我们使用的SX12xxDrivers-V2.1.0的LoRa库,库资源如下:

图1‑9 LoRa官方库
下面是官方库工程目录说明:
表 1-2工程目录结构
目录 | 说明 |
doc | 官方文档 |
lst | 工程编译时的临时文件目录 |
obj | 工程编译时的临时文件目录 |
src | 驱动以及可用硬件平台源码 |
sx12xxDrivers.rapp | Ride7 集成开发环境工程文件 (兼容 keil ARM) |
sx12xxDrivers.rprj | Ride7 集成开发环境工程文件 (兼容 keil ARM) |
我们只关注src源码目录和doc官方文档。
Platform:硬件平台相关源码
Radio:sx1232/sx1272/sx1276 驱动源码及无线驱动简易框架
main.c:数据 PING-PONG 收发示例代码
READEME.txt:库工程说明,这个很重要。
1.6.1 工程示例说明
本示例使用2台设备进行数据PING-PONG收发,一台作为主(master),一台做从机(slave),注意库中主从关系是自适配,不需要特意指定谁主谁从。
测试时若一台设备线上电运行,则自动成为主机,定时发送“PING”消息,另一台上电的设备一开始也是主机模式,当其收到“PING”消息后自动变为从机,并回应主机“PONG”数据,主机和从机之间不停的PING-PONG收发。
1.6.2 测试效果
模块底板上的LED发送指示灯和接收指示灯互相闪烁。当接收到数据并解析正确,则接收指示灯闪烁,发送数据完成,则发送指示灯闪烁。
下面从串口中显示测试效果:
当接收到数据包,会在串口上打印接收到的数据包。

图1‑10 打印接收到的信息
OnMater做主机运行代码,OnSlave做从机运行代码。下面红色代码打印接收信息。
COBOLCode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | void OnMaster( void ) { uint8_t i; switch( Radio->Process( ) ) { case RF_RX_TIMEOUT: // Send the next PING frame Buffer[0] = 'P'; Buffer[1] = 'I'; Buffer[2] = 'N'; Buffer[3] = 'G'; for( i = 4; i < BufferSize; i++ ) { Buffer[i] = i - 4; } Radio->SetTxPacket( Buffer, BufferSize ); break; case RF_RX_DONE: Radio->GetRxPacket( Buffer, ( uint16_t* )&BufferSize ); printf("master Rx_________%s\n",Buffer); if( BufferSize > 0 ) { if( strncmp( ( const char* )Buffer, ( const char* )PongMsg, 4 ) == 0 ) { // Indicates on a LED that the received frame is a PONG LedToggle( LED_GREEN ); // Send the next PING frame Buffer[0] = 'P'; Buffer[1] = 'I'; Buffer[2] = 'N'; Buffer[3] = 'G'; // We fill the buffer with numbers for the payload for( i = 4; i < BufferSize; i++ ) { Buffer[i] = i - 4; } Radio->SetTxPacket( Buffer, BufferSize ); } else if( strncmp( ( const char* )Buffer, ( const char* )PingMsg, 4 ) == 0 ) { // A master already exists then become a slave EnableMaster = false; LedOff( LED_RED ); } } break; case RF_TX_DONE: // Indicates on a LED that we have sent a PING LedToggle( LED_RED ); Radio->StartRx( ); break; default: break; } } /* * Manages the slave operation */ void OnSlave( void ) { uint8_t i; switch( Radio->Process( ) ) { case RF_RX_DONE: Radio->GetRxPacket( Buffer, ( uint16_t* )&BufferSize ); printf("slave Rx_________%s\n",Buffer); if( BufferSize > 0 ) { if( strncmp( ( const char* )Buffer, ( const char* )PingMsg, 4 ) == 0 ) { // Indicates on a LED that the received frame is a PING LedToggle( LED_GREEN ); // Send the reply to the PONG string Buffer[0] = 'P'; Buffer[1] = 'O'; Buffer[2] = 'N'; Buffer[3] = 'G'; // We fill the buffer with numbers for the payload for( i = 4; i < BufferSize; i++ ) { Buffer[i] = i - 4; } Radio->SetTxPacket( Buffer, BufferSize ); } } break; case RF_TX_DONE: // Indicates on a LED that we have sent a PONG LedToggle( LED_RED ); Radio->StartRx( ); break; default: break; } } |