智慧牧场项目笔记(LoRa开发低耗广域网)(2)
目录
LoRaPingPong系统设计
1、深入了解LoRa的技术原理
2、pingpong系统设计
3、系统功能开发
LoRa串口透传开发
1、串口透传系统设计
1.1 串口透传系统设计需求
1.2 系统通信机制
1.3 系统业务流程
2、串口透传系统功能开发
2.1 串口功能开发
2.2 LCD功能开发
2.3 无线收发功能开发
2.4 主函数开发
LoRaPingPong系统设计
1、深入了解LoRa的技术原理
1.1 LoRa扩频通信原理(了解)
无线通信技术:
模拟无线通信采用收音机为例, 其显著优势在于结构简单且相对容易实现。受噪声干扰较为明显, 导致信道质量较低, 其保密性能较弱。此外, 模拟无线通信技术的频谱利用率不高, 无法满足现代对高效、大容量通信系统的需求。伴随数字技术的进步, 模拟无线通信技术逐渐被数字化 wireless communication 技术逐步替代。关键步骤包括检波器、放大器以及功率放大器等环节;

数字无线通信:

总体而言,在信息处理过程中将二进制数据(01信号)编码为无线电磁波的形式是实现信息传递的关键步骤。主要有以下几种常见的调制方法:包括振幅位移键控、频率位移键控以及相位位移键控等技术手段。
无线通信的传播方式:简单来说有三种:(1)地波传播频率低于2\text{MHz};(2)天波传播属于频率范围2\text{MHz}至30\text{MHz};(3)直射传播适用于频率范围超过30\text{MHz}的无线电信号

其传播路径有:反射、散射、衍射。
下图是无线通信的噪声

由于扩频通信技术的出现,跳频技术能够有效抵御干扰并实现加密,在各种类型的噪声和多径干扰下能够保持安全。
基于LoRa技术,在信道编码过程中将用户端的数据与其对应的扩频码进行异或运算生成最终的发送序列。如图所示,在接收端接收序列后经过解码处理恢复出原始信息,并能够实现通信信道得以扩展,并显著提升了传输性能。

1.2 LoRa关键技术参数

(1)信号载波宽度BW:随着载波宽度BW的增长,在保证通信质量的前提下能够提高有效数据传输速率同时缩短通信时间;而对于采用LoRa芯片SX127x的实际应用而言,并非所有LoRa频段都采用双边频谱;FSK调制下的载波宽度仅指单边频谱
(2)扩频因子:用来描述信号在传输过程中扩展或分散程度的一个参数。在LoRa技术中,在这一过程中扩频因子决定了信号的扩散程度,在此过程中每个信息位会被扩展为多个"符号"。这样做主要是为了增强信号对抗干扰的能力并减少误码率的同时可能会影响传输速率。

(3)码率:码率(或信息传输效率)衡量了数据流中有效信息与冗余数据的比例。具体而言,它表示有效数据单元与发送的数据总量之比。当码率为k/n时,在每个n个符号中包含k个有意义的信息符号,其余n−k个符号属于冗余部分。LoRa系统采用循环纠错码实现前向错误检测和纠正功能,在这种机制下会产生额外的开销。
循环冗余校验码(RCPC)是一种用于数据传输中错误检测的有效方法。其基本机制在于,在发送端将原始数据流经过一个生成多项式进行处理,并计算出一个校验值(也称为CRC校验码或校验位),然后将其附加在原始数据流的末尾。接收端同样应用相同的生成多项式对带有校验值的数据流进行处理;如果能被整除,则确认传输过程中未发生错误;否则则认为存在传输错误。

LoRa符号速率Rs计算:
Rs=BW/(2^SF)
LoRa数据速率DR计算:
DR= SF*( BW/2^SF)*CR
LoRaWAN主要用于125kHz信号带宽设置,在其他专用协议中可以采用不同的信号带宽(BW)设置方案。调整BW、SF和CR参数从而会影响链路预算与传输时间分配,在电池续航时间和通信距离之间进行权衡取舍
参数设置:

1.3 LoRa数据收发任务
发送: 接收:


为了实现数据的收发官方固件库提供了下图的事件处理任务:

2、pingpong系统设计
2.1 系统设计需求
将LoRa终端划分为两种角色:Master和Slave
2.2 通信机制

2.3 系统业务流程
(1)初始化

(2)Master

(3)Slave

LoRa参数设置:

3、系统功能开发
3.1 IAR工程配置


3.2 搭建框架
(1)建立功能函数

建立四个函数:Master和Slave的LCD菜单显示信息、无线收发任务处理
//主机显示任务
void MLCD_Task(void)
{
}
//从机显示任务
void SLCD_Task(void)
{
}
//主机无线任务
void Master_Task(void)
{
}
//从机无线任务
void Slave_Task(void)
{
}
(2)建立数据结构

声明全部变量、进行赋值初始化
#define BUFFERSIZE 4
uint16_t BufferSize = BUFFERSIZE;
uint8_t PING_Msg[] = "PING";
uint8_t PONG_Msg[] = "PONG";
uint8_t Buffer[BUFFERSIZE];
//设备类型判断
#ifdef MASTER
uint8_t EnableMaster = true;
#else
uint8_t EnableMaster = false;
#endif
//收发数据个数
uint32_t Master_Txnumber = 0;
uint32_t Master_Rxnumber = 0;
uint32_t Slave_Txnumber = 0;
uint32_t Slave_Rxnumber = 0;
//获取无线收发的数据结构指针
tRadioDriver *radio = NULL;
3.3 编码
(1) 优化或更新主函数,并实现或配置显示Master和Slave标题的功能至新的显示功能模块中。
#ifdef MASTER
Gui_DrawFont_GBK16(0, 16, BLACK, BLUE, " Master ");
#else
Gui_DrawFont_GBK16(0, 16, BLACK, BLUE, " Slave ");
#endif
(2)完善上文新建任务函数
//主机显示任务
void MLCD_Show(void)
{
uint8_t str[20] = {0};
LCD_GPIO_Init();
sprintf((char *)str, "%d", Master_Rxnumber);
Gui_DrawFont_GBK16(45, 64, BLACK, RED, str);
memset((char *)str, 0, strlen((char*)str));
sprintf((char *)str, "%d", Master_Txnumber);
Gui_DrawFont_GBK16(45, 80, BLACK, RED, str);
HAL_SPI_DeInit(&hspi1);
MX_SPI1_Init();
}
//从机显示任务
void SLCD_Show(void)
{
uint8_t str[20] = {0};
LCD_GPIO_Init();
sprintf((char *)str, "%d", Slave_Rxnumber);
Gui_DrawFont_GBK16(45, 64, BLACK, RED, str);
memset((char *)str, 0, strlen((char*)str));
sprintf((char *)str, "%d", Slave_Txnumber);
Gui_DrawFont_GBK16(45, 80, BLACK, RED, str);
HAL_SPI_DeInit(&hspi1);
MX_SPI1_Init();
}
//主机无线任务
void Master_Task(void)
{
switch(radio->Process())
{
case RF_TX_DONE :
LedToggle(LED_TX);
Master_Txnumber++;
radio->StartRx();
break;
case RF_RX_DONE :
radio->GetRxPacket(Buffer, &BufferSize);
printf("Master_Task:RX____%s\n",Buffer);
if(strncmp((const char*)Buffer, (const char*)PONG_Msg, strlen((const char*)PONG_Msg)) == 0)
{
LedToggle(LED_RX);
Master_Rxnumber++;
radio->SetTxPacket(PING_Msg, strlen((const char*)PING_Msg));
}
break;
default :
break;
}
}
//从机无线任务
void Slave_Task(void)
{
switch(radio->Process())
{
case RF_TX_DONE :
LedToggle(LED_TX);
Slave_Txnumber++;
radio->StartRx();
break;
case RF_RX_DONE :
radio->GetRxPacket(Buffer, &BufferSize);
printf("Slave_Task:RX____%s\n",Buffer);
if(strncmp((const char*)Buffer, (const char*)PING_Msg, strlen((const char*)PING_Msg)) == 0)
{
LedToggle(LED_RX);
Slave_Rxnumber++;
radio->SetTxPacket(PONG_Msg, strlen((const char*)PONG_Msg));
}
break;
default :
break;
}
}
(3)main函数
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t RegVersion = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC_Init();
MX_USART1_UART_Init();
MX_SPI1_Init();
/* USER CODE BEGIN 2 */
Lcd_Init();
showimage(gImage_logo);
HAL_Delay(500);
Lcd_Clear(RED);
Gui_DrawFont_GBK16(0, 0, BLACK, BLUE, " LoRa Topology ");
#ifdef MASTER
Gui_DrawFont_GBK16(0, 16, BLACK, BLUE, " Master ");
#else
Gui_DrawFont_GBK16(0, 16, BLACK, BLUE, " Slave ");
#endif
Gui_DrawFont_GBK16(2, 48, BLACK, RED, "SSID:");
Gui_DrawFont_GBK16(45, 48, BLACK, RED, "30");
Gui_DrawFont_GBK16(2, 64, BLACK, RED, "RX:");
Gui_DrawFont_GBK16(2, 80, BLACK, RED, "TX:");
//SPI重新初始化
HAL_SPI_DeInit(&hspi1);
MX_SPI1_Init();
SX1276Read( REG_LR_VERSION, &RegVersion );
if(RegVersion != 0x12)
{
printf("LoRa read failed!\r\n");
printf("LoRa RegVersion:%d\r\n",RegVersion);
}
else
{
printf("LoRa read OK!\r\n");
printf("LoRa RegVersion:%d\r\n",RegVersion);
}
printf("system is init\r\n");
radio = RadioDriverInit();
radio ->Init();
#ifdef MASTER
radio->SetTxPacket(PING_Msg, strlen((const char*)PING_Msg));
printf("I am Master!\r\n");
#else
radio->StartRx();
printf("I am Slave!\r\n");
#endif
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(EnableMaster == true)
{
MLCD_Show();
Master_Task();
}
else
{
SLCD_Show();
Slave_Task();
}
}
/* USER CODE END 3 */
}
(4)LoRa驱动源码修改
在SX1276LoRaProcess()函数中,在case RFLR_STATE_RX_RUNNING的情况下对DIO2进行判断的语句以及在case RFLR_STATE_TX_RUNNING的情况下对DIO2进行判断的语句无需注释;同时基于上图提供的LoRa参数设置对以下源码进行修改
// Default settings
tLoRaSettings LoRaSettings =
{
470000000, // 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]
0, // RxSingleOn [0: Continuous, 1 Single]
0, // FreqHopOn [0: OFF, 1: ON]
4, // HopPeriod Hops every frequency hopping period symbols
1000, // TxPacketTimeout
1000, // RxPacketTimeout
4, // PayloadLength (used for implicit header mode)
};
编译过程中遇到了以下报错

解决方法:添加sx1276Fsk.c和sx1276FskMisc.c文件到radio文件夹
LoRa串口透传开发
1、串口透传系统设计
1.1 串口透传系统设计需求

将LoRa终端划分为两种角色:主从两种模式
任意字节大小(不超过128Byte)的数据可以从一个模块发送到另一个模块
借助串口调试助手实现接收与发送功能
该终端能够显示类型信息以及接收发送的数据包数量
1.2 系统通信机制

1.3 系统业务流程
(1)初始化

(2)主程序

(3)LCD任务

(4)串口接收任务

(5)无线任务

2、串口透传系统功能开发
2.1 串口功能开发
(1)串口功能接口

/* uart.c文件USART1 中断初始化 */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);//设置中断优先级
HAL_NVIC_EnableIRQ(USART1_IRQn);//打开全局中断
//main.c->启动串口1,使能串口空闲中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart1,a_Usart1_RxBuffer,RXLENGHT);
//中断函数
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint32_t temp;
//判断是否产生空闲中断
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET )
{
//清除中断标志
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
temp = huart1.Instance->ISR;
temp = huart1.Instance->RDR;
HAL_UART_DMAStop(&huart1); //停用DMA
temp = hdma_usart1_rx.Instance->CNDTR;
UsartType1.Usart_rx_len = RXLENGHT - temp;
HAL_UART_RxCpltCallback(&huart1);
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
//中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
uint8_t Old_len = 0;
if(UartHandle->Instance == USART1)//串口2接收数据向串口1发送
{
Old_len = strlen(UsartType1.usartDMA_rxBuf);
if(0 != Old_len) //如果DMA buff中还有数据,则接着存储在buff中
{
memcpy(&UsartType1.usartDMA_rxBuf[Old_len],a_Usart1_RxBuffer,UsartType1.Usart_rx_len);
Old_len=0;
}
else //接收数据串口1数据,
memcpy(UsartType1.usartDMA_rxBuf,a_Usart1_RxBuffer,UsartType1.Usart_rx_len);
// UsartxSendData_DMA(&huart1,a_Usart2_RxBuffer,UsartType2.Usart_rx_len);
memset(a_Usart1_RxBuffer,0,UsartType1.Usart_rx_len);
HAL_UART_Receive_DMA(&huart1,a_Usart1_RxBuffer,RXLENGHT);
UsartType1.receive_flag =1; //接收标识位
}
}
//串口数据获取
void UartDmaGet(void)
{
if(UsartType1.receive_flag == 1)//如果过新的数据
{
//串口接收到的数据原封发给SX1278
Radio->SetTxPacket(UsartType1.usartDMA_rxBuf, UsartType1.Usart_rx_len);
memset(UsartType1.usartDMA_rxBuf,0,UsartType1.Usart_rx_len);
UsartType1.receive_flag = 0; //接收数据标志清零,
}
}
(2)串口数据结构体

#define RXLENGHT 128
#define RECEIVELEN 2048
#define USART_DMA_SENDING 1//发送未完成
#define USART_DMA_SENDOVER 0//发送完成
typedef struct
{
uint8_t receive_flag ;//空闲接收标记
uint8_t dmaSend_flag ;//发送完成标记
uint16_t Usart_rx_len;//接收长度
uint8_t usartDMA_rxBuf[RECEIVELEN];//DMA接收缓存
}USART_RECEIVETYPE;
extern USART_RECEIVETYPE UsartType1;
extern USART_RECEIVETYPE UsartType2;
全局变量
extern uint8_t a_Usart1_RxBuffer[RXLENGHT];
2.2 LCD功能开发

该部分代码与PINGPONG系统主机从机LCD显示代码一致
2.3 无线收发功能开发

//接收数据包计数
void RxDataPacketNum(void)
{
if(EnableMaster == true)
Master_RxNumber++;
else
Slave_RxNumber++;
}
//发送数据包计数
void TxDataPacketNum(void)
{
if(EnableMaster == true)
Master_TxNumber++;
else
Slave_TxNumber++;
}
//读取sx127x射频射频数据
void Sx127xDataGet(void)
{
switch( Radio->Process( ) )
{
case RF_RX_TIMEOUT:
printf("RF_RX_TIMEOUT\n");
break;
case RF_RX_DONE:
Radio->GetRxPacket( Buffer, ( uint16_t* )&BufferSize );
if(EnableMaster == true)
printf("master Rx__%s,__,%d,%d\n",Buffer,strlen((char*)Buffer),BufferSize);
else
printf("slave Rx__%s,__,%d,%d\n",Buffer,strlen((char*)Buffer),BufferSize);
if( BufferSize > 0 )//&& (BufferSize == strlen((char*)Buffer)))
{
//接收数据闪烁
LedBlink( LED_RX );
//计算接收数据的个数
RxDataPacketNum();
//清空sx127x接收缓冲区
memset(Buffer,0,BufferSize );
}
break;
case RF_TX_DONE:
//发送闪烁
LedBlink( LED_TX );
//计算发送数据的个数
TxDataPacketNum();
Radio->StartRx( );
break;
case RF_TX_TIMEOUT:
printf("RF_TX_TIMEOUT\n");
break;
default:
break;
}
}
//声明一个led闪烁的函数
void LedBlink( tLed led )
{
LedPort[led]->ODR ^= LedPin[led];
HAL_Delay(50);
LedPort[led]->ODR ^= LedPin[led];
}
2.4 主函数开发
在PingPong系统的开发代码修改
#define BUFFERSIZE 128
//设备类型判断
#if defined(MASTER)
static uint8_t EnableMaster = true;
#elif defined(SLAVE)
static uint8_t EnableMaster = false;
#endif
//收发数据个数
static uint32_t Master_Txnumber = 0;
static uint32_t Master_Rxnumber = 0;
static uint32_t Slave_Txnumber = 0;
static uint32_t Slave_Rxnumber = 0;
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t RegVersion = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC_Init();
MX_USART1_UART_Init();
MX_SPI1_Init();
/* USER CODE BEGIN 2 */
Lcd_Init();
showimage(gImage_logo);
printf("sss\r\n");
HAL_Delay(500);
Lcd_Clear(RED);
Gui_DrawFont_GBK16(0, 0, BLACK, BLUE, " LoRa Topology ");
#ifdef MASTER
Gui_DrawFont_GBK16(0, 16, BLACK, BLUE, " Master ");
#else
Gui_DrawFont_GBK16(0, 16, BLACK, BLUE, " Slave ");
#endif
Gui_DrawFont_GBK16(2, 48, BLACK, RED, "SSID:");
Gui_DrawFont_GBK16(45, 48, BLACK, RED, "30");
Gui_DrawFont_GBK16(2, 64, BLACK, RED, "RX:");
Gui_DrawFont_GBK16(2, 80, BLACK, RED, "TX:");
//SPI重新初始化
HAL_SPI_DeInit(&hspi1);
MX_SPI1_Init();
//启动串口1,使能串口空闲中断
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart1,a_Usart1_RxBuffer,RXLENGHT);
SX1276Read( REG_LR_VERSION, &RegVersion );
if(RegVersion != 0x12)
{
printf("LoRa read failed!\r\n");
printf("LoRa RegVersion:%d\r\n",RegVersion);
}
else
{
printf("LoRa read OK!\r\n");
printf("LoRa RegVersion:%d\r\n",RegVersion);
}
//读到版本号关闭灯
LedOff( LED_RX );
LedOff( LED_TX );
LedOff( LED_NT );
radio = RadioDriverInit();
radio ->Init();
printf("system is init\r\n");
radio->StartRx();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
UartDmaGet();
Sx127xDataGet();
if(EnableMaster == true)
{
MLCD_Show();
}
else
{
SLCD_Show();
}
}
/* USER CODE END 3 */
}
