基于Lora通信的电表采集终端的实现

概要

基于Lora无线通信的电能表采集终端的实现

整体架构流程

网上扣的图(侵删),大致方案如此。

为啥用LoRa

LoRa无线视距可达3公里,LoRa无线模块接收灵敏度高其接收灵 敏度高达-140dBm,穿墙通信能力强,实测通信距离>11.5Km,完美解决了小数据量在复杂环境中的超远距通信问题。

LoRaTM 扩频调制解调技术,使器件传输距离远远超出现有的基于 FSK 或 OOK 调制方式的系统。在最大数据速率下,LoRaTM 的灵敏度要比 FSK 高 出 8dB;但若使用低成本材料和 20ppm 晶体的 LoRaTM,收发器灵敏度可以比 FSK 高出 20dB 以上。此外,LoRaTM 在选择性和阻塞性能方面也具有显著优势,可以进一步提高通信可靠度。同时,它还提供了很大的灵活性,用户可自行决定扩频调制带宽(BW)、扩频因子(SF) 和纠错率(CR)。扩频调制的另一优点就是,每个扩频因子均呈正交分布,因而多个传输信号可以占用同一信道而不互相干扰,并且能够与现有基于 FSK 的系统简单共存。

网上扣的图(侵删)

LoRa芯片

老产品用的SX1278,新产品用的SX1262,目前有没有更新的不清楚了。以下摘抄:

SX1262与SX1278、SX1276对比分析以及选型指南 - 知乎 (zhihu.com)
 

SX1262与SX1278、SX1276对比分析以及选型指南

产品简述

SX1278/6是Semtech公司在2013年推出的一款远距离、低功耗的无线收发器,是一款性能高的物联网无线收发器,具备特殊的LoRa调制方式,在一定程度上增加了通信距离;而SX1262是一款新产品,同样由Semtech公司在2018年推出,也具备特殊的LoRa调制方式。SX1262与SX1278/6在多个方面有些区别,下文主要进行三者的对比以及分析。

对比分析
  • 芯片封装、引脚

SX1278和SX1276的封装一致,封装均为6x6mm、28脚QFN封装,芯片体积相对比较大,再加上射频外设,因而能做出的射频模块体积相对也比较大。引脚方面仅有2个脚有区别,SX1278的第21、22脚均为GND,而SX1276的第21脚为RFI_HF,第22脚为RFO_HF。

SX1278引脚图

SX1276引脚图

SX1262的封装为4x4mm、24脚QFN封装,芯片体积相对比较小,可以做出的射频模块体积相对也比较小。引脚如下图所示。

SX1262引脚图

调制方式

SX1262与SX1278/6三者均带有多种调制方式,其中包含LoRa以及传统的(G)FSK调方式,LoRa调制方式本身就是这三种芯片的亮点。

  • 晶振电路及支持频段

SX1262与SX1278/6三者均可以采用TCXO晶振,若采用TCXO晶振,则XTB引脚不接,但SX1262的第6脚(DIO3)可用来为TCXO晶振供电,只需通过软件配置。采用XTAL时,SX1278/6外部需添加匹配电容;而SX1262外部无需添加匹配电容,内部已自带,可直接通过软件调节。

SX1278支持的频段为137-525MHz,射频发射输出脚为第27脚(PA_BOOST)或第28脚(RFO_LF),射频接收输入脚为第1脚(RFI_LF);SX1276支持的频段为137-1020MHz,当使用137-525MHz时,射频发射输出脚必须为第27脚(PA_BOOST)或第28脚(RFO_LF),射频接收输入脚必须为第1脚(RFI_LF),当使用862-1020MHz时,射频发射输出脚必须为第27脚(PA_BOOST)或第22脚(RFO_HF),射频接收输入脚必须为第21脚(RFI_HF)。由此看来,可以将SX1276理解为SX1278的加强版。

SX1262支持的频段为150-960MHz,射频发射输出脚为第23脚(RFO),射频接收差分输入脚分别为第21脚(RFI_P)、第22脚(RFI_N)。更换频段时,无需更换引脚,只需调整射频电路参数。

  • 配电及发射功率、接收灵敏度、电流

SX1278/6仅有一种配电方式,最大发射功率20dBm,要达到最大发射功率,需使用第27脚(PA_BOOST),发射电流120mA@20dBm。

SX1262最大发射功率可达22dBm,带有两种配电方式,低压差稳压器(LDO)以及高效率降压DC-DC转换器,可选择DC-DC形式,发射电流118mA@22dBm。

SX1278/6的接收电流约为12mA左右,SX1262在DC-DC方式下,接收电流约为5mA左右;三者能达到的最高灵敏度为-148dBm。

  • 扩频因子、空速等参数

在LoRa调制下,SX1278/6的扩频因子6-12,BW 7.8-500kHz,空中速率0.018-37.5kbps。而SX1262的扩频因子5-12,BW 7.81-500kHz,空中速率0.018-62.5kbps。可以看出在LoRa调制下,SX1262可以达到的空中速率要比SX1278/6大得多。

LoRaWAN

LoRaWAN网络通常采用星型拓扑结构,由拓扑中的网关来转发终端与后台网络服务器间的消息。网关通过标准IP连接来接入网络服务器,而终端则通过单跳的 LoRa 或者 FSK 来和一个或多个网关通讯。虽然主要传输方式是终端上行传输给网络服务器,但所有的传输通常都是双向的。

终端和网关间的通讯被分散到不同的信道频点和数据速率上。数据速率的选择需要权衡通信距离和消息时长两个因素,使用不同数据速率的设备互不影响。LoRa的数据速率范围可以从 0.3kbps 到 50kbps。为了最大程度地延长终端的电池寿命和扩大网络容量,LoRa网络使用速率自适应(ADR)机制来独立管理每个终端的速率和RF输出。

每个设备可以在任意可用的信道,任意时间,使用任意数据速率发送数据,只要遵守如下规定: 终端的每次传输都使用伪随机方式来改变信道。频率的多变使得系统具有更强的抗干扰能力。 终端要遵守相应频段和本地区的无线电规定中的最大发射占空比要求。 终端要遵守相应频段和本地区的无线电规定中的最大发射时长要求。

LoRaWAN是为LoRa远距离通信网络设计的一套通讯协议和系统架构。涉及比较多的协议,网上有相关讲解,这里不赘述。LoRaWAN也有相关的开源代码,可去git上找。

功能及实现

表计管理

采集器需对挂在485总线上的电表进行管理,需实现如下功能:

总线波特率适配。

理论上支持1200、2400、9600等常规波特率,大多数电能表的波特率为2400。采集器需自行进行匹配。适配过程为,采集器先用1200的波特率发起通信请求数据(读表),看是否能收到数据,有三种可能:

  1. 收到数据且数据通过数据帧校验。说明存在1200波特率的电表
  2. 收到数据但是没能通过数据校验。说明存在电表但是波特率不对,需换一个波特率
  3. 没收到数据。说明没有电表或者电表波特率不对(这个概率不大)

协议适配。

支持国网645协议的97版和07版。97版是老版本的表计协议,07版是新版本的表计协议,目前国网已经使用了698协议,暂不在本文描述中。

协议适配的方式是通过依次发送两个版本不同的数据请求帧,看是否能相应。如果相应到,则标记该版本。考虑到一般新协议会兼容老版本协议,所以我们优先发送新版本协议的请求数据。

static uint8_t AddrRequest_07[] = {0xfe,0xfe,0xfe,0xfe,0x68,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0x68,0x13,0x00,0xdf,0x16};//get meter addr
static uint8_t AddrRequest_97[] = {0xfe,0xfe,0xfe,0xfe,0x68,0x99,0x99,0x99,0x99,0x99,0x99,0x68,0x01,0x02,0x65,0xF3,0xC1,0x16};//get meter addr

电表号自动获取。

国网表的表号的ID为6个Byte的数据,国网资料中对地址的描述如下:

我们主要使用的是缩位寻址去自动识别表号。举个例子:

先用01 AA AA AA AA AA的地址去读表号,如果有且只有一个电表的表号是01 02 03 04 05 06,那你就能读到这个电表的表号。

复杂一点,加入485总线上有两块电表,表号分别为01 02 03 04 05 06  和 01 02 03 04 05 07,那就会复杂一些。需要先用01 AA AA AA AA AA读取表号,这个时候两个电表都会回数据,同时回数据会对485总线的数据进行冲突,即我们会获取到相应数据,但是这个数据无法通过帧校验,这个时候要继续用01 01 AA AA AA AA去获取表号,这个时候由于没有这个表,是无响应的,然后使用01 02 AA AA AA AA去获取表号,这个时候这个时候两个电表都会回数据,同时回数据会对485总线的数据进行冲突,我们就知道有两个以上的表是01 02 XXXXXX地址的表,这么继续搜下去,直到用01 02 03 04 05 06这个地址去搜,才能直到有这个表号的电表。

由于搜表是属于一个遍历的过程,所以可能时间会比较久。

表计注册

当搜表结束后,需将所有电表表号推送到服务器,由服务器对所有表计进行管理。节点段需将表计重新编码(1~32),重新编码的地址为短地址,6个byte的地址叫长地址。这里其实想做的是一次映射关系。当表计注册后,后续服务端发起抄表指令以及上报电量数据时均使用短地址即可。

表计关系存储

上面表述了一个电表的属性包括了短地址,长地址,协议类型。所以就可以定义数据结构了:

typedef struct sMeter_Addr_Arr
{
    uint8_t Protocol;
    uint8_t shortAddr;
    uint8_t longAddr[6];
} Meter_Addr_Arr_t;

typedef struct
{
   	uint8_t U8MeterNum;
	Meter_Addr_Arr_t Meter_Addr[32];
    uint8_t U8CheckSum;
} PACKED MeterAddr_t;

meterAddr_t就是存储在flash中的数据结构。每次搜表结束后将数据存在flash中,再每次重启的时候将表号都读出来。

Lora使用

片上资源

LoRa芯片需要如下片上资源才能运行:

  • spi
  • Dio对应的Gpio
  • timer

对一些配置进行说明

  • SPI配置,lora通信芯片使用的是spi通信,我们需配置spi的master模式,极性和相位(0,0),数据大小端msb、分频系数。同时,这个spi需要搭配几个IO一同使用,详见sx1278的手册了,这个io主要是来告诉你什么时候有数据的。
  • 频段。433/470/915,具体使用哪个频点其实是通过配置的掩码控制。
  • Class。主动上报用ClassA,需要下行控制,用classC,classB用得少。
  • DevEui、AppEui、Appkey。lora入网要使用的数据,我们是在生产模式的时候通过标签扫到配置程序中,然后写入到flash,然后重启后,从flash读到这些数据的。这些数据在入网的时候会用到LoRaMacJoinReq( DevEui, AppEui, AppKey );

射频芯片使用

对于射频芯片的使用这里抽象了一个方法Radio,在编译的时候选择所用芯片的.c,编译的时候,就会给定方法中的实现了:

/*!
 * Radio driver structure initialization
 */
const struct Radio_s Radio =
{
    SX1276Init,
    SX1276GetStatus,
    SX1276SetModem,
    SX1276SetChannel,
    SX1276IsChannelFree,
    SX1276Random,
    SX1276SetRxConfig,
    SX1276SetTxConfig,
    SX1276CheckRfFrequency,
    SX1276GetTimeOnAir,
    SX1276Send,
    SX1276SetSleep,
    SX1276SetStby, 
    SX1276SetRx,
    SX1276StartCad,
    SX1276ReadRssi,
    SX1276Write,
    SX1276Read,
    SX1276WriteBuffer,
    SX1276ReadBuffer
};
void RadioInit(void)
{
    printf("DevEui:%X %X %X %X - %X %X %X %X\r\n",DevEUI[0], DevEUI[1], DevEUI[2], DevEUI[3], DevEUI[4],DevEUI[5],DevEUI[6],DevEUI[7]);
    printf("LORA_SPREADING_FACTOR = %d\r\n",LORA_SPREADING_FACTOR);
    printf("AppVersion  = %s\r\n",AppVersion);
    printf("CompileDate = %s  %s\r\n",Compiledate,Compiletime);//20170830 hdl
    printf("-------------------------------------------------------\r\n");
    RadioEvents.TxDone = OnTxDone;
    RadioEvents.RxDone = OnRxDone;
    RadioEvents.TxTimeout = OnTxTimeout;
    RadioEvents.RxTimeout = OnRxTimeout;
    RadioEvents.RxError = OnRxError;

    Radio.Init( &RadioEvents );

    Radio.SetChannel( RF_Frequence[(DevEUI[7]&0x0F)]);

    Radio.SetPublicNetwork(false);
    Radio.SetTxConfig( MODEM_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, TX_TIMEOUT_VALUE );

    Radio.SetRxConfig( MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
                       LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
                       LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
                       0, true, 0, 0, LORA_IQ_INVERSION_ON, true );

    Radio.Rx( 0 );
}

LoRaMac配置

LoRaMac.c是定义符合lorawan协议的一些行为,包括入网、收发,组帧,这里简单讲述一些行为。

入网:

其他功能

停电上报。

监控电网波动。首先我们的芯片集成了一个电压比较器电路,当外部电源的电压存在一个压降的过程,会有产生一个中断,还有一个寄存器可以查到这个状态。我们的硬件电路中设计了一个比较大的电容。当外部供电失去时,电容可以提供一个短时间的供电。我们对外部供电的状态进行了去抖后,产生一个事件,外部供电异常,然后进入低功耗模式并开启了一个低功耗定时器,定时时间是110s到130s的随机值,这样可以防止同时启动。启动后进行上报掉电的数据。

上下行数据监控

lora入网状态监控

搜表状态监控

lora上行数据计数、下行数据计数,长时间未通信,启用linkcheck

485上行计数,下行计数,长时间为通信重启

驱动

串口驱动

驱动初始化

需配置串口的数据位、校验位、字节顺序、波特率、使能Tx、Rx中断、配置收发FIFO,收发各自FIFO

发送处理

由于采集器是主机,只有主机发起通信才会有从机相应。这里串口发送的驱动就比较简单,只需要把发送FIFO的数据给到串口的数据寄存器,并开启中断就好了

void Uart0_Send(unsigned char * data,unsigned short length)
{
	g_485RecOneFrameOver_count = 0;
#ifdef _485MODULE
    Assign_Value_Led(&Led_Red, 200, 2);
#endif
    PDBG(">>Send Date To Meter::\r\n");
    for(uint16_t i=0; i<length; i++)
    {
        if( IsFifoFull( &LPUartFifoTx ) == false )
        {
            // Read one byte from the receive data register
            FifoPush( &LPUartFifoTx, data[i] );
            PDBG("%02x ",data[i]);
        }
    }
    //PDBG("\r\n------------------------------------------------------------------\r\n");
    PDBG("\r\n");
    DrvUART0->Control(ARM_UART_INT_ENABLE, ARM_UART_INT_TX|ARM_UART_INT_RX);

    UART0->DATA = FifoPop(&LPUartFifoTx);

}
中断处理

中断处理只处理发送完成和接收。

发送完成后将FIFO的数据压到发送寄存器上,当发送完成后,关闭发送中断。

接收中断就将接收寄存器的数据读出来存到接收FIFO内,收到数据后开启个自减计数,防止未接收完,当计数清零时间内,没有再收到一包数据,就认为是一包收完了。

void ARM_LPUART_IRQHandler(void)
{
    uint8_t data;
    uint32_t uart_state;
    uint32_t uart_intsts;
//  UART_RESOURCES *uart=&UART0_Resources;


    /* Clear flag */
    uart_state = UART0->STATE;
    UART0->STATE = uart_state;
    uart_intsts = UART0->INTSTS;
    UART0->INTSTS = uart_intsts;

    /* Transmit Handle */
    if ((!(uart_state & UART_STATE_TXFULL))  && (UART0->CTRL & UART_CTRL_TXIE))
    {
#ifdef _485MODULE
        GpioWrite(&Tr_rs485, 1);
#endif
        if( IsFifoEmpty( &LPUartFifoTx ) == false )
        {
            data = FifoPop( &LPUartFifoTx );
            //  Write one byte to the transmit data register
            UART0->DATA =   data ;
        }
        else
        {
            // Disable the USART Transmit interrupt
            UART0->CTRL &= ~UART_CTRL_TXIE;
#ifdef _485MODULE
            DelayMs( TR_Delay );
            GpioWrite(&Tr_rs485, 0);
#endif
        }
    }

    /* Receive Handle */
    if ((uart_intsts & UART_INTSTS_RXIF) && (UART0->CTRL & UART_CTRL_RXIE) )
    {
        data =  UART0->DATA;
        if( IsFifoFull( &LPUartFifoRx ) == false )
        {
            // Read one byte from the receive data register
            FifoPush( &LPUartFifoRx, data );
        }
        if(((DeviceOperationStatus.stSearchMeter_finish == 0)||(DeviceOperationStatus.stMatchBaudrate_successed == 0)) && (Mode == APP_MODE))
        {
            g_485RecOneFrameOver_count = 1000;
			UartSend_WaitAck_TimeCount = 2000;
        }
        else
        {
            g_485RecOneFrameOver_count = 50;
        }
    }
}

SPI驱动(LoRa读写)

初始化

void SpiInit(void)
{
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_HARD_OUTPUT;
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    hspi1.Init.CRCPolynomial = 7;
    if (HAL_SPI_Init(&hspi1) != HAL_OK)
    {
        ErrorInfo();
    }
    __HAL_SPI_ENABLE(&hspi1);
}

读写

uint16_t SpiInOut( Spi_t *obj, uint16_t outData )
{
    if( ( obj == NULL ) || ( obj->Spi ) == NULL )
    {
        while( 1 );
    }
    
    while( __HAL_SPI_GET_FLAG(&hspi1, SPI_FLAG_TXE) == RESET );
    
    hspi1.Instance->DR = (uint32_t)outData;
    
    while( __HAL_SPI_GET_FLAG(&hspi1, SPI_FLAG_RXNE) == RESET );
    
    return ((uint16_t)(hspi1.Instance->DR));
    
}

软件实现

lora状态机

LoRa主要有如下几个状态:

  • 空闲
  • 入网请求
  • 入网成功等待搜表结束
  • 搜表结束上报数据
  • 收到数据
  • 停点主动上报

主业务流程都集中到收到数据处,其余均为阶段性状态。下行的搜表指令、透传指令、校时均由下行lora数据进行控制。

485状态机

485发送状态不在状态机中,由于我们是主机端,只有主机发起数据请求才会有相应数据。这里状态机只处理接收状态。串口中断中只负责接收数据放到fifo中并给出接收数据的状态。由状态机进行判定485状态有如下几个状态:

  • 等待收数据
  • 收到电表数据
  • 收到自定义协议数据(生产相关)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值