目录
LoRaPingPong系统设计
1、深入了解LoRa的技术原理
1.1 LoRa扩频通信原理(了解)
无线通信技术:
模拟无线通信:收音机的例子 ,优点在于它的结构简单,易于实现。由于模拟信号容易受到噪声的影响,导致通信质量不高,且保密性较差。此外,模拟无线通信技术的频带利用率较低,不能满足现代社会对于高效率和高容量的通信需求。因此,随着数字技术的发展,模拟无线通信技术逐渐被数字无线通信技术所取代。关键步骤:检波、放大、功率放大;
数字无线通信:
总结来说,将01信号转换为无线电磁波,有以下几种常见的调制方式:振幅位移键控、频率位移键控、相位位移键控。
无线通信的传播方式:简单来说就三种:(1)地波传播低于2MHZ(2)天波传播2MHZ~30MHZ(3)直线传播30MHZ以上
其传播路径有:反射、散射、衍射。
下图是无线通信的噪声
所以出现了扩频通信技术,这种跳频技术可以有效地抗击干扰和实现加密(在各种类型的噪声和多径是真中获得免疫)
LoRa是通过增大信号的带宽来提升信号的质量。如下图,将用户数据与扩频数据异或得到发送数据,而发送数据实现了增大了带宽并提高了信号的增益(质量)
1.2 LoRa关键技术参数
(1)信号带宽BW:BW的增大,可以提高有效数据的速率以缩短时间,前提是牺牲部分接收灵敏度的代价。但对于LoRa芯片SX127x,LoRa带宽为双边带宽(全信道带宽),而FSK调制方式的BW是指单边带宽
(2)扩频因子:描述信号在传输过程中如何扩散的一种参数。在LoRa技术中,扩频因子决定了信号在传输过程中的扩频程度,即把每个信息位扩展成多个“符号”,这样做是为了提高信号的抗干扰能力和降低误码率,同时牺牲一定的传输速率。
(3)编码率:编码率(或信息率)是数据流中有用部分(非冗余)的比例。也就是说,有效数据位与传输的总数据位的比例。如果编码率是k/n,则对每k位有用信息,编码器总共产生n位的数据,其中n-k是多余的。LoRa采用循环纠错编码进行前向错误检测与纠错。使用该方式会产生传输开销。
Tip:循环纠错编码(是一种差错检测编码技术,核心思想是在发送方将数据流通过一个生成多项式进行处理,从而生成一个校验值(也称为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
Master主动发送PING数据,接收PONG数据
Slave如果接收到PING数据,回应PONG数据
终端在LCD屏幕上显示终端类型及收发数据包个数
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)修改main函数以及将显示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终端定义成两种角色:Master和Slave
一个模块发送任意字节长度(小于128Byte)数据,另一模块都可以接收到
PC机上通过串口调试助手实现接收和发送
终端在LCD屏幕上显示终端类型及收发数据包个数
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 */
}