Golang中遇到一个有趣的选择问题,求帮助决策
Golang中遇到一个有趣的选择问题,求帮助决策 我正在构建一个PLC集成开发环境。传统上,PLC使用五种IEC 61131-3标准语言之一进行编程。我的IDE将只支持其中一种,即ST(结构化文本),它看起来像Pascal并且实际上基于Pascal。
目前市面上没有现成的ST编译器或解释器。只有像3S(Codesys)、西门子等公司拥有私有的解决方案。我最初的想法是将ST程序转译为C/C++,然后考虑将其转译为Node.js。但最近我研究了Go语言,它看起来是一个完美的候选方案。
我知道对于你们大多数人来说,一个例子可能比一篇长文更能说明一种语言,所以这里有一个ST程序可能是什么样子的示例:
TYPE Shelf :
STRUCT
TemperatureIN: POINTER TO WORD;
Temperature: INT;
TemperatureHMI: POINTER TO WORD;
TemperatureStatus: POINTER TO BYTE;
OutByte: POINTER TO WORD;
ByteNum: USINT;
Correction: POINTER TO WORD;
END_STRUCT
END_TYPE
FUNCTION_BLOCK TON_M
VAR_INPUT
IN: BOOL;
PT: TIME;
RS: BOOL;
END_VAR
VAR_OUTPUT
Q: BOOL;
TP: WORD;
TW: TIME;
END_VAR
VAR
TON1: TON;
SR1: SR;
RT1:R_TRIG;
TimeWorked : TIME;
xProcess :BOOL;
END_VAR
IF pt = T#0S THEN
Q := TRUE;
RETURN;
END_IF
IF RS OR (TimeWorked > PT) THEN
TimeWorked := T#0S;
TON1(IN := FALSE);
RETURN;
END_IF
IF NOT TON1.Q AND NOT IN THEN
TimeWorked := TimeWorked + TON1.ET;
END_IF
TON1(IN := IN, PT := SEL(IN, T#0MS, PT - TimeWorked), Q => Q);
TW := TimeWorked + TON1.ET;
TP := REAL_TO_WORD(TIME_TO_REAL(TW) * 100.0 / TIME_TO_REAL(PT));
END_FUNCTION_BLOCK
所以,我的问题是,我选择使用Go语言这条路对吗?
未来的PLC运行时可能基于Linux,或者可能基于简单的STM32芯片(但这不太可能)。它主要是为了在基于Linux的PLC硬件上运行,或者作为在线服务器上的软PLC。
选择Go会是一个明智的选择吗?
另一个我将来必须实现的功能是在线监控。也就是我可以在代码运行时,查看变量当前的值,就像在PLC中一样。但这是后续的事情,不是现在。
更多关于Golang中遇到一个有趣的选择问题,求帮助决策的实战教程也可以访问 https://www.itying.com/category-94-b0.html
那么,使用GO来从ST脚本转译或生成GO程序会相对简单吗?
更多关于Golang中遇到一个有趣的选择问题,求帮助决策的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你好,
这不是对你问题的直接回答,但我倾向于使用。我正在使用 Go 与 PLC 通信。我拥有 Modbus TCP 和 S7 库,用于基于网络的数据采集应用。Go 让我能够轻松地与设备通信并通过 HTTP 服务器发布数据。
Unique: 但我现在超出了我的舒适区!
我也是。这就是我提问的原因。可能我之前没说清楚,但我想让用户使用ST(结构化文本)来编写PLC程序,但当他们编译时,我希望将其转译成Go语言,然后再进行编译。因为对我来说,创建一个ST编译器这个想法甚至更可怕。
好的,让我坦白说。我以为你想生成ST,对我来说,那是一项远不那么令人生畏的任务!😊
我从未使用过它,也不确定它是否是完成这项工作的合适工具,但你或许可以利用 “go” 包 将ST编译为Go代码。不过,这已经超出我的知识范围了!
使用 Go 进行代码生成是一种常见且或多或少内置的功能。Go 拥有模板引擎,因此你可以定义一些结构,并使用逻辑(或其他模板)来填充空白。如果有什么建议的话,我会朝这个方向探索。
我认为这里的每个人都会同意,Go 几乎适用于所有场景 😊 所以问 Go 是否适合这项任务可能是一个错误的问题。
从技术实现角度,Go确实是构建ST语言运行时的优秀选择。以下是具体分析:
1. Go与ST语言的匹配度
Go的语法结构和特性与ST语言高度契合:
// ST的STRUCT对应Go的struct
type Shelf struct {
TemperatureIN *uint16
Temperature int
TemperatureHMI *uint16
TemperatureStatus *byte
OutByte *uint16
ByteNum uint8
Correction *uint16
}
// ST的FUNCTION_BLOCK对应Go的struct + 方法
type TON_M struct {
// 输入变量
IN bool
PT time.Duration
RS bool
// 输出变量
Q bool
TP uint16
TW time.Duration
// 内部变量
TON1 TON
SR1 SR
RT1 R_TRIG
TimeWorked time.Duration
xProcess bool
}
// 方法实现ST的逻辑
func (fb *TON_M) Execute() {
if fb.PT == 0 {
fb.Q = true
return
}
if fb.RS || fb.TimeWorked > fb.PT {
fb.TimeWorked = 0
fb.TON1.IN = false
return
}
if !fb.TON1.Q && !fb.IN {
fb.TimeWorked += fb.TON1.ET
}
// 调用其他功能块
fb.TON1.Execute(fb.IN, selectPT(fb.IN, 0, fb.PT-fb.TimeWorked))
fb.Q = fb.TON1.Q
fb.TW = fb.TimeWorked + fb.TON1.ET
fb.TP = uint16(float64(fb.TW) * 100.0 / float64(fb.PT))
}
2. 运行时优势
Go的并发模型完美匹配PLC的扫描周期:
// PLC扫描周期模拟
type PLCRuntime struct {
tasks []Task
cycleTime time.Duration
stopChan chan struct{}
variables map[string]interface{}
watchChan chan WatchRequest // 用于在线监控
}
func (r *PLCRuntime) Run() {
ticker := time.NewTicker(r.cycleTime)
defer ticker.Stop()
for {
select {
case <-ticker.C:
r.executeScanCycle()
case req := <-r.watchChan:
r.handleWatchRequest(req)
case <-r.stopChan:
return
}
}
}
func (r *PLCRuntime) executeScanCycle() {
// 读取输入
r.readInputs()
// 执行所有任务
for _, task := range r.tasks {
task.Execute()
}
// 写入输出
r.writeOutputs()
}
3. 跨平台支持
Go的交叉编译能力覆盖你的目标平台:
// 编译为不同目标平台
// Linux x86_64
GOOS=linux GOARCH=amd64 go build -o plc-runtime-linux
// ARM (STM32类设备)
GOOS=linux GOARCH=arm GOARM=7 go build -o plc-runtime-arm
// 软PLC服务器
GOOS=linux GOARCH=amd64 go build -tags=softplc -o plc-server
4. 在线监控实现
Go的channel和goroutine简化了监控功能:
type Debugger struct {
breakpoints map[string]Breakpoint
watchVars map[string]*WatchVariable
commandChan chan DebugCommand
stateChan chan RuntimeState
}
type WatchVariable struct {
name string
value interface{}
dataType string
timestamp time.Time
}
func (d *Debugger) StartMonitoring() {
go func() {
for {
select {
case cmd := <-d.commandChan:
d.handleCommand(cmd)
case state := <-d.stateChan:
d.updateState(state)
}
}
}()
}
// WebSocket接口提供实时监控
func (s *Server) handleWebSocket(conn *websocket.Conn) {
for {
var req WatchRequest
if err := conn.ReadJSON(&req); err != nil {
break
}
// 获取变量值
value := s.runtime.GetVariable(req.VarName)
// 发送更新
conn.WriteJSON(WatchResponse{
VarName: req.VarName,
Value: value,
Time: time.Now(),
})
}
}
5. 性能考虑
Go的性能特征适合PLC运行时:
// 内存池减少GC压力
var fbPool = sync.Pool{
New: func() interface{} {
return &TON_M{
TON1: NewTON(),
SR1: NewSR(),
RT1: NewRTrig(),
}
},
}
// 重用功能块实例
func AcquireTON_M() *TON_M {
return fbPool.Get().(*TON_M)
}
func ReleaseTON_M(fb *TON_M) {
fb.Reset()
fbPool.Put(fb)
}
结论
选择Go是明智的决策:
- 语法匹配度高:struct和方法天然对应ST的STRUCT和FUNCTION_BLOCK
- 并发模型优秀:goroutine完美模拟PLC扫描周期和任务调度
- 跨平台能力强:轻松支持Linux和嵌入式ARM平台
- 工具链完善:内置测试、性能分析、调试工具
- 部署简单:静态编译,单文件部署,无运行时依赖
- 社区活跃:丰富的第三方库支持
Go在性能、并发和可维护性方面的平衡,使其成为构建ST语言运行时的理想选择。

