当前位置:   article > 正文

揭秘PLC(2)

plc的编译过程及特点

书接上回,我们上次以Codesys为例,介绍了其PC端的安装和Target配置,今天我们继续聊聊PLC的基本原理和实现方法。

突然想到之前有个重要的问题没有跟大家介绍,从实现方式上讲,PLC分编译型和解释型,Codesys是编译型的,而某宝上200多元的“三菱仿”是解释型的,其实这“三菱仿”某宝上20元就能买到全套原理图及PCB和源码。

71851480f06e3ee6e48204336f00bd1a.png

这两条技术路线还是有比较大的区别,其中最大的区别就是IDE生成的文件是否可以在PLC设备端直接执行,编译型的可以直接运行,而解释型生成的是中间文件,其主要包含命令码和操作码,PLC在获取中间文件后会根据预先定义好的命令码来执行相应的操作。以后有时间我们分析下那20元的代码。今天我们先以编译型为例,来剖析其内部的工作原理。

区别编译型解释型
执行效率
开发难度
跨平台运行
反编译源文件一般
无扰下装
防克隆

1. 编译型PLC

编译型PLC本质上就是PC端IDE(如之前介绍的Codesys)生成的固件或者二进制文件可以直接在PLC设备端运行,这就要求PC端IDE要集成相关的编译器。为了更容易说明这个问题,我们以开源PLC软件Beremiz为例讲解:

4b00afae4142fca52d6fcf88fcf398de.png

Beremiz的上位机的核心由3部分组成:PLCOpen Editor,MatIEC,GCC

组件功能
PLCOpen Editor为用户提供编程界面和配置信息
MatIEC将用户基于IEC61131-3程序转为C语言文件
GCC将MatIEC转换的C文件编译链接为可执行的二进制代码或elf文件

Codesys对比Beremiz其实没有本质区别,可以理解为Codesys PC端 = PLCOpen Editor+MatIEC+GCC,核心过程是一致的,都是先将用户程序、配置信息编译到Image中,只是这个过程都在Codesys PC端内部处理了,并没有打开让用户看。不过,我们还是可以从一些文件中看到一些端倪。在Project目录中可以看到一个bin文件(不同的target目标文件不同)

caa1257ef5b81a7957b5b22032a3cbd3.png

用二进制工具打开后,可以看到如下内容,第一个字是保留字,第二个字是Image的地址,第三个字是初始化函数指针

1e55fd1d4e445f5e4991b1b9455495df.png

不同的平台可以选择不同的编译器,在目标设置中可以看到它支持的处理器平台:

ff1e64b3b4aac115b54cd975c9d591ad.png

2c6559ba31b545a62845da865d76320f.png

e0234448b2ff9a6bc35cee1ba9d06ac0.png

眼尖的小伙伴会看到Intel StrongARM,这是个什么鬼,Intel还有ARM产品么?还真有,Intel XScale系列产品是以ARMv4/ARMv5TE内核为基础的增强型ARM,不过后来停产了,由于ARM9用的ARMv4T内核与其指令兼容,所以理论上Codesys V2.x也是支持ARM9的。

efd7020ec4ef5e61f96b36cb05a6b12e.png

2. Runtime System

Codesys/Beremiz编译好固件后是怎么运行在PLC设备端的呢?这就要请出今天的主角Runtime System(RTS)。由于没有公开的资料,所以只能以Beremiz为例向大家介绍其中的奥秘。下图就是RTS核心的一些功能:

5dfab8541e26b20c054e318d11c64748.png

PLC RTS功能
IO主要指CPU本体所带的IO通道,常见的有DI, DO, AI, AO, PWM, PTO, HCI等等
Dbg Server主要用于和PC端通讯,获取下载用户程序,登录/注销调试模式,调试模式下读/写变量,示波器等功能
Library库分两种,内部库是用户通过IEC61131编写的供其他用户使用,外部库是写在RTS中并提供头文件给PC端
User Code InterfaceRTS的主要功能,配合PC端来运行用户的程序
Backplane Bus背板总线主要用于控制扩展的IO,常见的协议有Modbus、Profibus等等

RTS有一个非常简单的主循环,首先初始化MCU外设,然后加载用户代码并初始化变量,最后进入While(1)循环:IO输入->用户代码执行->IO输出->处理服务

61b753ed6351d008d992618c9a5db18b.png

2.1 User Code Interface

既然是用户接口,我们先来看看相关代码,Beremiz会将用户代码插入到对应的main.c中,然后进行编译:

eb0721bbf786e97b7c9266733f54a98d.png

接口是通过下面结构体与RTS进行交互的:

  1. typedef struct
  2. {
  3. uint32_t * sstart;
  4. app_fp_t entry;
  5. //App startup interface
  6. uint32_t * data_loadaddr;
  7. uint32_t * data_start;
  8. uint32_t * data_end;
  9. uint32_t * bss_end;
  10. app_fp_t * pa_start;
  11. app_fp_t * pa_end;
  12. app_fp_t * ia_start;
  13. app_fp_t * ia_end;
  14. app_fp_t * fia_start;
  15. app_fp_t * fia_end;
  16. //RTE Version control
  17. //Semantic versioning is used
  18. uint32_t rte_ver_major;
  19. uint32_t rte_ver_minor;
  20. uint32_t rte_ver_patch;
  21. //Hardware ID
  22. uint32_t hw_id;
  23. //IO manager data
  24. plc_loc_tbl_t * l_tab; //Location table
  25. uint32_t * w_tab; //Weigth table
  26. uint16_t l_sz; //Location table size
  27. //Control instance of PLC_ID
  28. const char * check_id; //Must be placed to the end of .text
  29. //App interface
  30. const char * id; //Must be placed near the start of .text
  31. int (*start)(int ,char **);
  32. int (*stop)(void);
  33. void (*run)(void);
  34. void (*dbg_resume)(void);
  35. void (*dbg_suspend)(int);
  36. int (*dbg_data_get)(unsigned long *, unsigned long *, void **);
  37. void (*dbg_data_free)(void);
  38. void (*dbg_vars_reset)(void);
  39. void (*dbg_var_register)(int, void *);
  40. uint32_t (*log_cnt_get)(uint8_t);
  41. uint32_t (*log_msg_get)(uint8_t, uint32_t, char*, uint32_t, uint32_t*, uint32_t*, uint32_t*);
  42. void (*log_cnt_reset)(void);
  43. int (*log_msg_post)(uint8_t, char*, uint32_t);
  44. }
  45. plc_app_abi_t;

初始化加载用户代码,PLC_APP_BASE就是用户Image在MCU中对应的Flash地址

  1. uint8_t plc_load_app()
  2. {
  3. uint8_t ret = 0;
  4. if(plc_app_is_valid())
  5. {
  6. plc_curr_app = ((plc_app_abi_t *)PLC_APP_BASE);
  7. plc_app_cstratup();
  8. ret = 1;
  9. }
  10. else
  11. {
  12. plc_curr_app = (plc_app_abi_t *)&plc_app_default;
  13. ret = 0;
  14. }
  15. return ret;
  16. }

cstratup函数原型,其过程和MCU进main函数之前的初始化代码非常相似,清零bss段,全局变量赋值等等

  1. void plc_app_cstratup(void)
  2. {
  3. volatile uint32_t *src, *dst, *end;
  4. app_fp_t *func, *func_end;
  5. //Init .data
  6. dst = plc_curr_app->data_start;
  7. end = plc_curr_app->data_end;
  8. src = plc_curr_app->data_loadaddr;
  9. while (dst < end)
  10. {
  11. *dst++ = *src++;
  12. }
  13. //Init .bss
  14. end = plc_curr_app->bss_end;
  15. while (dst < end)
  16. {
  17. *dst++ = 0;
  18. }
  19. // Constructors
  20. // .preinit_array
  21. func = plc_curr_app->pa_start;
  22. func_end = plc_curr_app->pa_end;
  23. while (func < func_end)
  24. {
  25. (*func)();
  26. func++;
  27. }
  28. // .init_array
  29. func = plc_curr_app->ia_start;
  30. func_end = plc_curr_app->ia_end;
  31. while (func < func_end)
  32. {
  33. (*func)();
  34. func++;
  35. }
  36. }

初始化完成后,已经可以进入while(1)了,通过plc_curr_app->run()函数指针就可以运行用户程序了

  1. while (1)
  2. {
  3. dbg_handler();
  4. if(plc_state == PLC_STATE_STARTED)
  5. {
  6. plc_iom_get();
  7. if((g_u64timer - before_iec) >= g_u64tick_period)
  8. {
  9. plc_curr_app->run();
  10. before_iec = g_u64timer;
  11. }
  12. plc_iom_set();
  13. }
  14. }

今天就写到这里吧,改天继续。

未完待续

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/577885
推荐阅读
相关标签
  

闽ICP备14008679号