【IoT应用创新大赛】LoRaWAN在工业互联网中的应用

2020-04-23 11:21:12 浏览数 (1)

视频内容
LoRaWAN在工业互联网中的应用.ppt

1、选题背景

工业互联网,是智能制造发展的基础,可以提供共性的基础设施和能力;我国已经将工业互联网作为重要基础设施,为工业智能化提供支撑。

国内智能制造选择是一条“中间路线”,将工业互联网作为重要基础设施,为工业智能化提供支撑。在国内这种道路选择中,工业互联网可能发挥更大的作用。

一方面,国内有着较强的工业制造能力,产业门类齐全,产业链完整,大量的工厂、车间和生产线具备网联化的潜力,但是自动化水平同德国相差较大。

另一方面,国内物联网、人

工智能、云计算、移动互联、大数据等技术在迅速发展,工业互联网同国际水平的差距,明显小于传统的自动化、数控领域。

因此,在智能制造体系中,智慧工厂、智慧车间、智慧产线国内也会重点做,但是其中会加大工业互联网技术的应用,充分利用国内信息化的优势,弥补在自动化等传统领域的短板。

工业互联网对智能制造的支撑作用工业互联网对智能制造的支撑作用

利用工业互联网平台,企业能迅速调整供应链,及早转产、复产;即便很多员工无法返岗,智能互联工厂也能正常运转,100%按时完成订单任务;设备维护不会受到交通管制影响,远程诊断基本解决问题……

现实是最好的教科书。越来越多的制造业企业意识到工业互联网已不再是可有可无的“噱头”,而是发展进程中不可或缺的必由之路。

2、方案设计

本方案使用LoRaWAN网络将工业现场中的控制器、电表、以及环境传感器数据上云;实现远程监控、控制功能;在工业现场中,因为大部分设备都是使用的Modbus、CAN总线协议,所以本方案中使用Modbus协议的采集链路;通过Modbus读取工业设备中的数据,然后将数据通过LoRaWAN发送到云端,然后通过小程序进行展示、并且可以通过小程序实现对机电设备的启停、开关机等功能,系统框架如下:

系统框架系统框架

3、 硬件设计

下图是我们本次使用的硬件环境,我们使用三种类型的板子进行开发,绿色的板子是我们自己设计的一个Lora的节点,使用SPI驱动的;本次实训我们直接使用板载的温湿度传感器进行环境数据采集,然后通过串口外接串口转485对Modbus设备进行数据采集:

硬件硬件

由于我们没办法去工业现场连接真正的Modbus-RTU设备,我们这里使用Modbus Slave软件进行模拟;模拟部分如下:

Modbus模拟软件配置Modbus模拟软件配置

我们能够看见,我这里一共模拟了四个设备,期间从站地址1的设备包含了03功能码的保持寄存器、以及01功能码的线圈状态;地址2是我们的电表;地址3压力传感器;地址4是流量计;我们通过节点驱动Modbus来对各个数据进行采集。

我这里使用的SPI驱动的Lora板子原理图如下,需要注意的是,我没用直接使用semtech公司的SX127XSX126X芯片,而是使用的安信可的Ra-01,这里也是直接使用的SPI驱动,原理图如下图所示:

硬件原理图硬件原理图

PCBA的3D视图如下图所示:

PCBA 3D视图PCBA 3D视图

安信可 LoRa 系列模块是安信可科技基于 SX1278 设计开发的,主要采用LoRa™远程调制解调器,用于超长距离扩频通信,抗干扰性强,能够最大限度降低电流消耗。借助 SEMTECH 的 LoRa™ 专利调制技术,SX1278 具有超过 -148dBm 的高灵敏度, 18dBm 的功率输出,传输距离远,可靠性高。同时,相对传统调制技术,LoRa™ 调制技术在抗阻塞和选择方面也具有明显优势,解决了传统设计方案无法同时兼顾距离、抗干扰和功耗的问题。Ra-01如下图所示:

Ra-01Ra-01

4、 软件设计

4.1 LoraWAN驱动程序设计

关于LoraWAN的驱动程序部分,我们这里一共分为两种类别,一种是使用AT指令进行驱动的LoraWAN模组,另一种是直接驱动SPI的Lora芯片进行通讯的;关于驱动部分我们只做简单讲述,后期有时间了之后我会考虑详细讲述下关于使用SPI驱动Lora芯片的移植方法;我们直接在假设我们已经移植好了LoraWAN协议的情况下,根据我们网关的实际情况来进行修改;

网关配置如:

LoraWAN网关配置LoraWAN网关配置

我们能够看见我们网关的八个信道,对应的,我们也需要将我们的Lora节点设置到对应的信道,我们首先找到RegionCN470.c,然后找到RegionCN470InitDefaults函数,我们添加一个数组用来存放频段,然后根据信道计算出我们设置的每个频段的信号号,将我们对应的频段设置号,在设置信道前,我们首先需要将所有信道屏蔽掉,设置完成后函数如下:

代码语言:javascript复制
1.uint32_t UserFreq[8]={486300000,486500000,486700000,486900000,487100000,487300000,487300000,487700000};  
2. 
3.void RegionCN470InitDefaults( InitDefaultsParams_t* params )  
4.{  
5.    Band_t bands[CN470_MAX_NB_BANDS] =  
6.    {  
7.        CN470_BAND0  
8.    };  
9. 
10. switch( params->Type )  
11.    {  
12. case INIT_TYPE_INIT:  
13.        {  
14. // Initialize bands 
15.            memcpy1( ( uint8_t* )NvmCtx.Bands, ( uint8_t* )bands, sizeof( Band_t ) * CN470_MAX_NB_BANDS );  
16. 
17. // Channels 
18. // 125 kHz channels 
19.//            for( uint8_t i = 0; i < CN470_MAX_NB_CHANNELS; i   ) 
20.//            { 
21.//                NvmCtx.Channels[i].Frequency = 470300000   i * 200000; 
22.//                NvmCtx.Channels[i].DrRange.Value = ( DR_5 << 4 ) | DR_0; 
23.//                NvmCtx.Channels[i].Band = 0; 
24.//            } 
25. 
26.//            // Initialize the channels default mask 
27.//            NvmCtx.ChannelsDefaultMask[0] = 0xFFFF; 
28.//            NvmCtx.ChannelsDefaultMask[1] = 0xFFFF; 
29.//            NvmCtx.ChannelsDefaultMask[2] = 0xFFFF; 
30.//            NvmCtx.ChannelsDefaultMask[3] = 0xFFFF; 
31.//            NvmCtx.ChannelsDefaultMask[4] = 0xFFFF; 
32.//            NvmCtx.ChannelsDefaultMask[5] = 0xFFFF; 
33.            NvmCtx.ChannelsDefaultMask[0] = 0x0000;  
34.            NvmCtx.ChannelsDefaultMask[1] = 0x0000;  
35.            NvmCtx.ChannelsDefaultMask[2] = 0x0000;  
36.            NvmCtx.ChannelsDefaultMask[3] = 0x0000;  
37.            NvmCtx.ChannelsDefaultMask[4] = 0x0000;  
38.            NvmCtx.ChannelsDefaultMask[5] = 0x0000;  
39. 
40. for( uint8_t i = 0,j=0; i < 8; i   )  
41.            {  
42.                j=(UserFreq[i]-470300000 )/200000;//计算信道号 
43. 
44.                NvmCtx.Channels[j].Frequency= UserFreq[i] ;  
45.                NvmCtx.Channels[j].DrRange.Value=( DR_5 << 4 ) | DR_0;   
46.                NvmCtx.Channels[j].Band = 0;  
47. 
48.                NvmCtx.ChannelsDefaultMask[j/16]|=1<<(j);//相应的信道掩码位设置为1 
49.            }  
50. 
51. // Update the channels mask 
52.            RegionCommonChanMaskCopy( NvmCtx.ChannelsMask, NvmCtx.ChannelsDefaultMask, 6 );  
53. break;  
54.        }  
55. case INIT_TYPE_RESTORE_CTX:  
56.        {  
57. if( params->NvmCtx != 0 )  
58.            {  
59.                memcpy1( (uint8_t*) &NvmCtx, (uint8_t*) params->NvmCtx, sizeof( NvmCtx ) );  
60.            }  
61. break;  
62.        }  
63. case INIT_TYPE_RESTORE_DEFAULT_CHANNELS:  
64.        {  
65. // Restore channels default mask 
66.            RegionCommonChanMaskCopy( NvmCtx.ChannelsMask, NvmCtx.ChannelsDefaultMask, 6 );  
67. break;  
68.        }  
69. default:  
70.        {  
71. break;  
72.        }  
73.    }  
74.}  

我们在入网前还需要确定好使用的入网方式以及设备ID、密钥等相关参数,进入到Commissioning.h中进行设置,我这里使用的是APB的入网方式,所以我们直接把空中激活的宏OVER_THE_AIR_ACTIVATION设置为0,以及设置为将DEV_EUI设置为静态,完成之后我们需要填写LORAWAN_DEVICE_EUI、LORAWAN_APP_KEY、LORAWAN_NWK_KEY、LORAWAN_DEVICE_ADDRESS这几个参数。

我们使用AT的模组时也要使用APB方式入网,同样的需要将上面的第四个参数填写完成。

4.2 LoraWAN应用程序编写

关于应用层,我们使用的SPI或者AT命令驱动都是一样的,我这里直接创建一个了个Modbus采集的任务,函数如下:

代码语言:javascript复制
1.void modbus_app(void *arg)  
2.{  
3.    ModbusMaster_begin();  
4.    MX_USART3_UART_Init();  
5.    comm.SlaveID = 0x01;            //Modbus从设备地址 
6.    comm.u16ReadAddress = 0x00;     //读取地址 
7.    comm.u16ReadQty = 8;             //读线圈数量 
8.    comm.u8MBFunction = 0x03;       //功能字 
9. while(1){  
10. //读PLC 保持寄存器数据 地址2:运行状态 
11.        comm.SlaveID = 0x01;            //Modbus从设备地址 
12.        comm.u16ReadAddress = 0x02;     //读取地址 
13.        comm.u16ReadQty = 1;             //读线圈数量 
14.        comm.u8MBFunction = 0x03;       //功能字 
15. if(ModBusMasterCommunication(comm) == 0x00) {  
16.            dev_data_wrapper.u.dev_data.runState = ModbusMaster_getResponseBuffer(0);  
17.            printf("Read 01 03:%drn", dev_data_wrapper.u.dev_data.runState );  
18.        }  
19. //读电表数据 地址1-6 
20.        comm.SlaveID = 0x02;  
21.        comm.u8MBFunction = 0x03;  
22.        comm.u16ReadQty = 6;  
23.        comm.u16ReadAddress = 0x01;  
24. if(ModBusMasterCommunication(comm) == 0x00) {  
25.            dev_data_wrapper.u.dev_data.A_Voltage = ModbusMaster_getResponseBuffer(0);  
26.            dev_data_wrapper.u.dev_data.B_Voltage = ModbusMaster_getResponseBuffer(1);  
27.            dev_data_wrapper.u.dev_data.C_Voltage = ModbusMaster_getResponseBuffer(2);  
28.            dev_data_wrapper.u.dev_data.A_Current = ModbusMaster_getResponseBuffer(3);  
29.            dev_data_wrapper.u.dev_data.B_Current = ModbusMaster_getResponseBuffer(4);  
30.            dev_data_wrapper.u.dev_data.C_Current = ModbusMaster_getResponseBuffer(5);  
31.            printf("电表读取成功n");  
32.        }  
33. //读压力传感器数据 地址1 
34.        comm.SlaveID = 0x03;  
35.        comm.u8MBFunction = 0x03;  
36.        comm.u16ReadQty = 1;  
37.        comm.u16ReadAddress = 0x01;  
38. if(ModBusMasterCommunication(comm) == 0x00) {  
39.            dev_data_wrapper.u.dev_data.Pressure = ModbusMaster_getResponseBuffer(0);  
40.            printf("压力传感器读取成功n");  
41.        }  
42. //读流量计数据 地址1 
43.        comm.SlaveID = 0x04;  
44.        comm.u8MBFunction = 0x04;  
45.        comm.u16ReadQty = 1;  
46.        comm.u16ReadAddress = 0x01;  
47. if(ModBusMasterCommunication(comm) == 0x00) {  
48.            dev_data_wrapper.u.dev_data.Flowmeter = ModbusMaster_getResponseBuffer(0);  
49.            printf("流量计读取成功n");  
50.        }  
51.        tos_task_delay(1000);  
52.    }  
53.}  

我这里封装了一下采集程序的结构体,我们直接设置结构体中的对应的值进能够实现对Modbus的读写命令了。结构体结构如下图所示:

Modbus读写结构体Modbus读写结构体

由于篇幅有限,我们就不对Modbu做过多讲解,我们设置好结构体之后我们使用ModBusMasterCommunication函数进行读写即可,当返回值为0x00即表示读写成功,然后通过ModbusMaster_getResponseBuffer读取对应数值的数据即可;

Lora上报数据的结构体如下图所示:

Lora数据结构体Lora数据结构体

我们使用另一个任务定时发送Lora数据,如下图所示:

代码语言:javascript复制
1.void application_entry(void *arg)  
2.{  
3.    rhf76_lora_init(HAL_UART_PORT_0);  
4.    tos_lora_module_recvcb_register(recv_callback);  
5.    rhf76_join_abp("XXX", "XXX","XXX","XXX");  
6. 
7.    (void)tos_task_create(&task_modbus, "task_prio5", modbus_app,  
8.                  NULL, 0,  
9.                  stack_task_modbus, STK_SIZE_TASK_MODBUS, 0);  
10. while (1) {  
11.        E53_IA1_Read_Data();  
12.        printf("Lux:%f,Humi:%f,Temp:%f,Motor:%d,Light:%d", E53_IA1_Data.Lux,E53_IA1_Data.Humidity,E53_IA1_Data.Temperature,E53_IA1_Data.MotorMode,E53_IA1_Data.LightMode);  
13. 
14.        dev_data_wrapper.u.dev_data.temperature = E53_IA1_Data.Temperature;  
15.        dev_data_wrapper.u.dev_data.humidity    = E53_IA1_Data.Humidity;  
16.        dev_data_wrapper.u.dev_data.period      = report_period;  
17. 
18.        tos_lora_module_send(dev_data_wrapper.u.serialize, sizeof(dev_data_t));  
19.        tos_task_delay(report_period * 1000);  
20.    }  
21.}  
rhf76_join_abp中传入的数据是我们前面说的那四个参数;
Lora的接收回调函数如下所示:
1.void recv_callback(uint8_t *data, uint8_t len)  
2.{  
3. int i = 0;  
4. 
5.    printf("len: %dn", len);  
6. 
7. for (i = 0; i < len;   i) {  
8.        printf("data[%d]: %dn", i, data[i]);  
9.    }  
10. 
11. if(data[0] != 0 | data[1] != 0 )  
12.        report_period = data[0] | (data[1] << 8);  
13. if(data[2] != 0 | data[3] != 0 )  
14.        runState = data[2] | (data[3] << 8);  
15. if(data[4] != 0)  
16.        runFlag = data[4];  
17.    printf("report_period: %d runState: %d,runFlag: %dn", report_period,runState,runFlag);  
18.    comm.u16WriteAddress = 0x07;  
19.    comm.u8MBFunction = 0x06;  
20.    comm.u16WriteValue = runState;  
21. if(ModBusMasterCommunication(comm) == 0x00) {  
22.        printf("write data sucess");  
23.    }  
24. if(runFlag == 1)  
25.        light_control(1);  
26. else if(runFlag == 2)  
27.        light_control(0);  
28.}  

我们这里直接将运行状态同通过06功能码写入到对应位置。

4.3 腾讯物联网平台配置

上面我们说了关于Lora数据的结构体以及下发的解析程序,我们来到腾讯物联网平台创建好对应的产品,然后将我们定义的传感器数据在云端定义好,最后就是解析部分,上行解析代码如下:

代码语言:javascript复制
1.function RawToProtocol(fPort, bytes) {  
2.    var data = {  
3. "method": "report",  
4. "clientToken" : new Date(),  
5. "params" : {}  
6.    };  
7.    data.params.temperature = bytes[0];  
8.    data.params.humidity = bytes[1];  
9.    data.params.period = bytes[2] | (bytes[3] << 8);  
10.    data.params.runState = bytes[4];  
11.    data.params.Pressure = bytes[5] | (bytes[6] << 8);  
12.    data.params.A_Voltage = bytes[7] | (bytes[8] << 8);  
13.    data.params.B_Voltage = bytes[9] | (bytes[10] << 8);  
14.    data.params.C_Voltage = bytes[11] | (bytes[12] << 8);  
15.    data.params.A_Current = bytes[13];  
16.    data.params.B_Current = bytes[14];  
17.    data.params.C_Current = bytes[15];  
18.    data.params.Flowmeter = bytes[16] | (bytes[17] << 8);  
19. return data;  
20.}  

下行解析代码如下:

代码语言:javascript复制
1.function ProtocolToRaw(obj) {  
2.    var data = new Array();  
3.    data[0] = 5;// fport=5 
4.    data[1] = 0;// unconfirmed mode 
5.    data[2] = obj.params.period & 0x00FF;  
6.    data[3] = (obj.params.period >> 8) & 0x00FF;  
7.    data[4] = obj.params.runState & 0x00FF;  
8.    data[5] = (obj.params.runState >> 8) & 0x00FF;  
9.    data[6] = obj.params.runFlag?1:2;  
10. return data;  
11.}  

最后我们布局好小程序,然后给设备上电,我们就能够在云端看见我们的设备采集上来的数据,可以在云端对设别进行控制了。

5、 实验现象

我们将程序烧写到我们的硬件之后,因为我们的硬件是没有485电平转换芯片的,如果我们需要接真正的机器设备的话,我们还需要对硬件进行修改,增加一个ttl串口转485的电路,下面推荐一个485电路,这个电路我们可以不用去考虑485的收发状态,还增加了光耦隔离,跟用普通的串口一样:

485原理图485原理图

我们这里不直接接机电设别,我们直接使用PC端对设备进行模拟,如下图所示:

实验现象实验现象
小程序页面显示小程序页面显示

0 人点赞