1、选题背景
工业互联网,是智能制造发展的基础,可以提供共性的基础设施和能力;我国已经将工业互联网作为重要基础设施,为工业智能化提供支撑。
国内智能制造选择是一条“中间路线”,将工业互联网作为重要基础设施,为工业智能化提供支撑。在国内这种道路选择中,工业互联网可能发挥更大的作用。
一方面,国内有着较强的工业制造能力,产业门类齐全,产业链完整,大量的工厂、车间和生产线具备网联化的潜力,但是自动化水平同德国相差较大。
另一方面,国内物联网、人
工智能、云计算、移动互联、大数据等技术在迅速发展,工业互联网同国际水平的差距,明显小于传统的自动化、数控领域。
因此,在智能制造体系中,智慧工厂、智慧车间、智慧产线国内也会重点做,但是其中会加大工业互联网技术的应用,充分利用国内信息化的优势,弥补在自动化等传统领域的短板。
利用工业互联网平台,企业能迅速调整供应链,及早转产、复产;即便很多员工无法返岗,智能互联工厂也能正常运转,100%按时完成订单任务;设备维护不会受到交通管制影响,远程诊断基本解决问题……
现实是最好的教科书。越来越多的制造业企业意识到工业互联网已不再是可有可无的“噱头”,而是发展进程中不可或缺的必由之路。
2、方案设计
本方案使用LoRaWAN网络将工业现场中的控制器、电表、以及环境传感器数据上云;实现远程监控、控制功能;在工业现场中,因为大部分设备都是使用的Modbus、CAN总线协议,所以本方案中使用Modbus协议的采集链路;通过Modbus读取工业设备中的数据,然后将数据通过LoRaWAN发送到云端,然后通过小程序进行展示、并且可以通过小程序实现对机电设备的启停、开关机等功能,系统框架如下:
3、 硬件设计
下图是我们本次使用的硬件环境,我们使用三种类型的板子进行开发,绿色的板子是我们自己设计的一个Lora的节点,使用SPI驱动的;本次实训我们直接使用板载的温湿度传感器进行环境数据采集,然后通过串口外接串口转485对Modbus设备进行数据采集:
由于我们没办法去工业现场连接真正的Modbus-RTU设备,我们这里使用Modbus Slave软件进行模拟;模拟部分如下:
我们能够看见,我这里一共模拟了四个设备,期间从站地址1的设备包含了03功能码的保持寄存器、以及01功能码的线圈状态;地址2是我们的电表;地址3压力传感器;地址4是流量计;我们通过节点驱动Modbus来对各个数据进行采集。
我这里使用的SPI驱动的Lora板子原理图如下,需要注意的是,我没用直接使用semtech公司的SX127XSX126X芯片,而是使用的安信可的Ra-01,这里也是直接使用的SPI驱动,原理图如下图所示:
PCBA的3D视图如下图所示:
安信可 LoRa 系列模块是安信可科技基于 SX1278 设计开发的,主要采用LoRa™远程调制解调器,用于超长距离扩频通信,抗干扰性强,能够最大限度降低电流消耗。借助 SEMTECH 的 LoRa™ 专利调制技术,SX1278 具有超过 -148dBm 的高灵敏度, 18dBm 的功率输出,传输距离远,可靠性高。同时,相对传统调制技术,LoRa™ 调制技术在抗阻塞和选择方面也具有明显优势,解决了传统设计方案无法同时兼顾距离、抗干扰和功耗的问题。Ra-01如下图所示:
4、 软件设计
4.1 LoraWAN驱动程序设计
关于LoraWAN的驱动程序部分,我们这里一共分为两种类别,一种是使用AT指令进行驱动的LoraWAN模组,另一种是直接驱动SPI的Lora芯片进行通讯的;关于驱动部分我们只做简单讲述,后期有时间了之后我会考虑详细讲述下关于使用SPI驱动Lora芯片的移植方法;我们直接在假设我们已经移植好了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的读写命令了。结构体结构如下图所示:
由于篇幅有限,我们就不对Modbu做过多讲解,我们设置好结构体之后我们使用ModBusMasterCommunication函数进行读写即可,当返回值为0x00即表示读写成功,然后通过ModbusMaster_getResponseBuffer读取对应数值的数据即可;
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的收发状态,还增加了光耦隔离,跟用普通的串口一样:
我们这里不直接接机电设别,我们直接使用PC端对设备进行模拟,如下图所示: