前言
作为我心中报警器的首选,肯定是需要一个通信手段的,无线通信无疑是方便又愉快的选择,但是又要考虑低功耗,干扰,传输距离,信号强度等等因素。而LoRa专利调制技术无疑是一种较好的解决方案,拥有低功耗与长通信距离的优点。用在这类小电器上很是舒服。
关于LoRa
LoRa是一种线性调频扩频的物联网调制技术,也叫宽带现象调频(Chirp Modulation)技术。相对于传统的FSK技术,LoRa在相同的功耗下具有更长的传输距离,且抗干扰能力强。
1、LoRa SPI驱动
直接弄了个安信可的模块开整,安信可的模块是直接与LoRa的芯片通信,采用的是SPI接口
SPI我采用的是SPI1,具体IO如下:
SPI采用主从模式,CPOL:LOW,CPHA:1Edge,8位数据位,FirstBit:MSB,具体驱动代码如下:
void bsp_SPI_Init(void)
{
hspi.Instance = SPIx;
hspi.Init.Mode = SPI_MODE_MASTER;
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi.Init.Direction = SPI_DIRECTION_2LINES;
hspi.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi.Init.DataSize = SPI_DATASIZE_8BIT;
hspi.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi.Init.TIMode = SPI_TIMODE_DISABLE;
hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi.Init.CRCPolynomial = 7;
hspi.Init.CRCLength = SPI_CRC_LENGTH_8BIT;
hspi.Init.NSS = SPI_NSS_SOFT;
hspi.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
if (HAL_SPI_Init(&hspi) != HAL_OK)
{
Error_Handler();
}
}
void HAL_SPI_MspInit(SPI_HandleTypeDef *_hspi)
{
GPIO_InitTypeDef gpio_init;
SPIx_SCK_CLK_ENABLE();
SPIx_MISO_CLK_ENABLE();
SPIx_MOSI_CLK_ENABLE();
SPIx_CLK_ENABLE();
gpio_init.Pin = SPIx_SCK_PIN;
gpio_init.Mode = GPIO_MODE_AF_PP;
gpio_init.Pull = GPIO_PULLUP;
gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
gpio_init.Alternate = SPIx_SCK_AF;
HAL_GPIO_Init(SPIx_SCK_PORT, &gpio_init);
gpio_init.Pin = SPIx_MISO_PIN;
gpio_init.Alternate = SPIx_MISO_AF;
HAL_GPIO_Init(SPIx_MISO_PORT, &gpio_init);
gpio_init.Pin = SPIx_MOSI_PIN;
gpio_init.Alternate = SPIx_MOSI_AF;
HAL_GPIO_Init(SPIx_MOSI_PORT, &gpio_init);
}
关于SPI的时钟速率方面:
升特官方的文档也详细的写出最高为16Mhz,而我们的C0芯片最高频率为48Mhz,4分频随便跑
2 LoRa芯片驱动
升特官方给出了LoRa驱动的接口,安信可也有根据官方接口移植的驱动工程,为了方便快捷测试功能,直接就使用安信可的驱动文件进行移植,根据驱动工程分析,需要三个IO口RST,D1,D4来控制LoRa芯片的状态,定义为:
//LoRa的RST复位脚,PA8
#define RADIO_nRESET_PIN GPIO_PIN_8
#define RADIO_nRESET_PORT GPIOA
#define RADIO_nRESET_CLOCK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
//LoRa的DIO1功能脚,PA15
#define RADIO_DIO1_PIN GPIO_PIN_15
#define RADIO_DIO1_PORT GPIOA
#define RADIO_DIO1_CLOCK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define RADIO_DIO1_EXINT_LINE EXTI_LINE_15
//LoRa的BUSY功能脚(对应sx127x的DIO4),PC7
#define RADIO_DIO4_BUSY_PIN GPIO_PIN_7
#define RADIO_DIO4_BUSY_PORT GPIOC
#define RADIO_DIO4_BUSY_CLOCK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
其中D1功能脚需要使用到外部中断模式,其他两个脚当成正常GPIO进行设置就行。
D1设置如下:
void SX126xIoIrqInit(DioIrqHandler dioIrq)
{
GPIO_InitTypeDef gpio_init;
dio1IrqCallback = dioIrq;
/* 使能 DIO1 的时钟与外部中断时钟*/
RADIO_DIO1_CLOCK_ENABLE();
/* DIO1 */
gpio_init.Pin = RADIO_DIO1_PIN;
gpio_init.Mode = GPIO_MODE_IT_RISING; //输入模式
gpio_init.Pull = GPIO_PULLUP; //上拉输入
gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH; //强驱动模式
HAL_GPIO_Init(RADIO_DIO1_PORT, &gpio_init);
HAL_NVIC_SetPriority(EXTI4_15_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(EXTI4_15_IRQn);
}
之前的标准库设置外部中断非常麻烦,直接只需要在GPIO_InitTypeDef结构体里面设置就行了,我这里设置的是上升边沿中断GPIO_MODE_IT_RISING,直接就搞定了。函数名称为升特提供的接口名称
D4与Nss设置如下:
void SX126xIoInit(void)
{
GPIO_InitTypeDef gpio_init;
/* 使能 BUSY 脚时钟*/
RADIO_DIO4_BUSY_CLOCK_ENABLE();
/* 使能 NSS 时钟 */
RADIO_NSS_CLK_ENABLE();
gpio_init.Pin = RADIO_DIO4_BUSY_PIN;
gpio_init.Mode = GPIO_MODE_INPUT;
gpio_init.Pull = GPIO_PULLUP;
gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(RADIO_DIO4_BUSY_PORT, &gpio_init);
gpio_init.Pin = RADIO_NSS_PIN;
gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init.Pull = GPIO_PULLUP;
gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(RADIO_NSS_PORT, &gpio_init);
SPIx_NSS_Set();
}
因为SPI1上只有LoRa模块一个设备,所以在设置完成片选引脚的状态后,直接就选中LoRa芯片了
最后RST引脚设置:
void SX126xReset(void)
{
GPIO_InitTypeDef gpio_init;
SX126xDelayMs(10);
/* 使能 nRESET 脚时钟*/
RADIO_nRESET_CLOCK_ENABLE();
gpio_init.Pin = RADIO_nRESET_PIN;
gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init.Pull = GPIO_PULLUP;
gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(RADIO_nRESET_PORT, &gpio_init);
HAL_GPIO_WritePin(RADIO_nRESET_PORT, RADIO_nRESET_PIN, GPIO_PIN_RESET);
SX126xDelayMs(20);
HAL_GPIO_WritePin(RADIO_nRESET_PORT, RADIO_nRESET_PIN, GPIO_PIN_SET);
SX126xDelayMs(10);
}
三个IO口简单轻松搞定,然后LoRa芯片还需要一个计时器用来统计收发时间,直接选TIM3作为计时器,具体如下:
void SX126xTimerInit(void)
{
/* 使能 LoRaTMR */
LoRa_TIM_CLOCK_ENABLE();
TimHandle.Instance = LoRa_TIM;
TimHandle.Init.Prescaler = 10;
TimHandle.Init.Period = SystemCoreClock / 10000;
TimHandle.Init.ClockDivision = 0;
TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
TimHandle.Init.RepetitionCounter = 0;
TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
{
Error_Handler();
}
/* 两个软件定时器清零 */
txTimerHandle.isRun = 0;
rxTimerHandle.isRun = 0;
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 2);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
HAL_TIM_Base_Start_IT(&TimHandle);
}
最后再把外部中断与定时器的中断函数编写上,直接就能开始玩LoRa透传了。我直接使用了安信可提供的DEMO,核心函数如下:
void ExampleSX126xSendDemo(void){
uint8_t OCP_Value = 0;
printf("start %s() example\r\n",__func__);
SX126xRadioEvents.TxDone = SX126xOnTxDone;
SX126xRadioEvents.RxDone = SX126xOnRxDone;
SX126xRadioEvents.TxTimeout = SX126xOnTxTimeout;
SX126xRadioEvents.RxTimeout = SX126xOnRxTimeout;
SX126xRadioEvents.RxError = SX126xOnRxError;
Radio.Init( &SX126xRadioEvents );
Radio.SetChannel(LORA_FRE);
Radio.SetTxConfig( MODEM_LORA, LORA_TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
LORA_SPREADING_FACTOR, LORA_CODINGRATE,
LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
true, 0, 0, LORA_IQ_INVERSION_ON, 3000 );//参数:lora模式,发射功率,fsk用的lora设置为0就可以,带宽,纠错编码率,前导码长度,固定长度数据包(一般是不固定的所以选false),crc校验,0表示关闭跳频,跳频之间的符号数(关闭跳频这个参数没有意义),这个应该是表示是否要翻转中断电平的,超时时间
OCP_Value = Radio.Read(REG_OCP);
printf("[%s()-%d]read OCP register value:0x%04X\r\n",__func__,__LINE__,OCP_Value);
Radio.SetRxConfig( MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
LORA_SX126X_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
0, true, 0, 0, LORA_IQ_INVERSION_ON, false );
printf("freq: %d\r\n Tx power: %d\r\n band width: %d\r\n FS: %d\r\n CODINGRATE: %d\r\n PREAMBLE_LENGTH: %d\r\n",LORA_FRE,LORA_TX_OUTPUT_POWER,LORA_BANDWIDTH,LORA_SPREADING_FACTOR,LORA_CODINGRATE,LORA_PREAMBLE_LENGTH);
while(1){
Radio.IrqProcess( ); // Process Radio IRQ
Radio.Send( (uint8_t *)MSG, strlen(MSG) );
bsp_DelayMS( 3000 );
}
}
安信可是把发送和接收分开了,我还是图方便,两个全部进行了初始化,然后在下面的while(1)大循环中调用了IrqProcess进程,最后发送一个MSG(aithinker888)。
要与这个芯片进行通信,必须括频率、扩频因子、带宽、编码率,前导码长度、跳频使能、同步字(syncword)、低速率优化这些选项全部相同,不过经过测试前导码长度这块,只需要接收方长度大于发送方就能让芯片解析收到的数据包了。总体设置如下:
#define LORA_FRE 433000000 //收发频率
#define LORA_TX_OUTPUT_POWER 20 //发射功率,126x发射功率0~22dbm,127x发射功率2~20dbm
#define LORA_BANDWIDTH 0 //带宽,sx126x:[0:125kHz, 1:250kHz, 2:500kHz, 3:Reserved]
#define LORA_SPREADING_FACTOR 7 //扩频因子,范围7~12
#define LORA_CODINGRATE 1 //纠错编码率。[1:4/5, 2:4/6, 3:4/7, 4: 4/8]
#define LORA_PREAMBLE_LENGTH 8 //前导码长度,范围6~65536
#define LORA_SX126X_SYMBOL_TIMEOUT 0 //symbols(sx126x用到的是0,127x用到的是5)
#define LORA_FIX_LENGTH_PAYLOAD_ON false //是否为固定长度包(暂时只有sx126x用到了)
#define LORA_IQ_INVERSION_ON false //设置是否翻转中断电平(暂时只有sx126x用到了)
#define LORA_RX_TIMEOUT_VALUE 3000 //接收超时时间
全部设置完成之后,再弄一块接收板,参数设置相同就可以开始接收数据了。
上电信息如下:
芯片sx126x的OCP寄存器数值为0x38,安信可并没有更新,但是升特有说明
同步收发如下:
【LoRa透传测试视频】 https://www.bilibili.com/video/BV1Bq421w77b/?share_source=copy_web&vd_source=348ed027c500f7bd59a4eb77f3c8f510
简单的测试,使用视频方便看出同步收发
总结
STM32的库经过这么多年的积累,现在使用起来的感受,就初步使用来说,已经是非常舒服了。在我需要使用spi驱动lora芯片中,还加入了外部中断与定时器计时,还算是有点复杂,但是在使用Hal库,大部分只需要关注软件逻辑就行,还是那句话这是ST喊出替代8位芯片的底气之一。
接下来,我准备使用C0芯片的通信接口加上简单的协议制作一个Bootloader,完成物联网入门第一步