AnyMath 文档
Notebook

Arduino代码生成(十字路口)

在本示例中,我们将使用 Engee 开发一个模型,基于 Arduino 兼容开发板并利用 有限自动机 库,来控制同一十字路口上的两盏汽车信号灯和一盏行人信号灯。

引言

本示例的目标是开发一个十字路口控制模型,该路口由两条机动车道和一条人行道组成,分别由两组三相交通信号灯和一组双相交通信号灯进行控制。 各相位的切换算法将根据时序图确定。控制算法将通过多个Chart 模块实现,而输入信号的采集和输出信号的生成则由C-Function 模块完成。

在控制程序的算法中,可选择工作模式或待机模式,模式切换将根据来自目标设备的离散输入信号进行。

本示例是在演示示例“Arduino代码生成(基于有限自动机的交通信号灯)”的延伸。与前一个示例相比,本示例的特点包括:对多个相互关联的模块进行建模和代码生成Chart ,以及将数据数组输出到外设。

硬件部分

本演示示例使用了 Amperka 公司的 Iskra Neo 控制器。 受控电路由无线电元件(LED、按钮触点、电阻和连接线)在无焊面包板上组装而成,如下方布局图所示。 该电路模拟了交通信号灯的工作原理:包括两个三相汽车信号灯和一个两相行人信号灯。

image.png

待机模式的开启/关闭输入信号通过按钮触点传输至Arduino开发板的第4号数字引脚。汽车交通信号灯各相位的开启输出信号通过第8至13号数字引脚输出至相应颜色的LED。 行人信号灯各相位的启用输出信号则通过数字引脚6、7输出。为确保电路正常工作,还使用了限流电阻(330 欧姆)和拉高电阻(10 千欧姆)。 该电路的电源由Arduino主板直接供电。

时序图

所实现的算法将根据下图所示的时序图,模拟十字路口交通信号灯的工作状态。

三个交通流——两个机动车流和一个行人流——采用异步控制。在此过程中,每次仅对其中一个交通流同时给予通行许可(绿灯),且持续时间为10秒。 机动车流的通行许可结束后,绿色灯区将开始闪烁。 交通信号灯从通行信号切换至禁止通行信号(红灯)时,黄灯会亮起5秒;反向切换时,红灯和黄灯会同时亮起5秒。

image_3.png

待机模式的循环由交通信号灯的两种状态描述:

  • 1秒内,机动车信号灯的黄色灯区亮起,行人信号灯的灯区熄灭;
  • 1秒内,机动车和行人交通信号灯的所有灯区均熄灭。

模型描述

本演示示例的模型如下图所示。

image_2.png

控制算法分为几个功能部分:计数器的工作在模块Common_Counter 中实现;用于机动车交通信号灯的控制信号light 的生成在模块Traffic_Lights 中实现;用于行人交通信号灯的控制信号cross 的生成在模块Crosswalk 中实现。

将模式选择信号传输至模型使用模块Digital Input ,将消息输出至 Arduino 串行端口使用模块To Serial ,而将机动车和行人交通信号灯的代码输出至 Arduino 数字引脚则分别使用模块Digital_Output_1Digital_Output_2

为了将数据有结构地输出到串行端口,使用了向量生成模块DataMux

状态图

在模块Common_Counter 的状态图中,通过状态Counter 以及转换23 ,模拟了递增计数器在 54 秒后重置的工作过程。 状态Service 和转换1 用于在交叉路口进入待机模式时,将计数器重置为“-1”。

image_2.png

根据计数器变量cntr 的数值,在模块Traffic_Lights 的状态图中,会选择变量lights 的数值,该变量编码了汽车交通信号灯各灯区的状态。此时,除ninthtenth 以外的所有状态,均再现了工作模式下各灯区切换的顺序。
Traffic_Lights 模块的状态图如下图所示。

image_2.png

进而,根据模块Crosswalk 中变量lights 的值,选择编码行人交通信号灯状态的变量cross 的值。
模块Crosswalk 的状态图如下图所示。

image.png

在每个模块的状态图设置中Chart 都定义了信号表。输出变量的初始值如下:

  • 模块Common_Counter ,变量mode = 0
  • 模块Traffic_Lights ,变量light = 33
  • 模块Crosswalk ,变量cross = 2

仿真结果

加载上述模型:

In [ ]:
if "arduino_crossroad" in [m.name for m in engee.get_all_models()]
    m = engee.open( "arduino_crossroad" );
else
    m = engee.load( "$(@__DIR__)/arduino_crossroad.engee" );
end

data = engee.run(m);

根据模型生成的数据,绘制计数器变量counter 、机动车交通信号灯各相位编码状态traffic_lights 以及行人交通信号灯crosswalk_lights 的曲线图。

In [ ]:
using Plots
plotlyjs()
plot(data["counter"].time, data["counter"].value,
    label="计数器", size=(900,300), lw=2, st=:step)
plot!(data["traffic_lights"].time, data["traffic_lights"].value,
    label="交通信号灯代码", size=(900,300), lw=2, st=:step,
    legend=:topleft)
xlabel!("时间,秒")
ylabel!("含义")
Out[0]:
In [ ]:
plot(data["crosswalk_lights"].time, data["crosswalk_lights"].value,
    label="行人交通信号灯代码", size=(900,300), lw=2, st=:step, color=:green,
    ylims = (0,3), xticks=:none, legend=:topleft )
    xlabel!("时间,秒")
ylabel!("含义")
Out[0]:

从生成的图表中可以观察到变量的变化。模型中的计算周期设定为1秒。机动车和行人交通信号灯各分区状态变量traffic_lightscrosswalk_lights 的数值及持续时间变化,与时间图表相吻合

将代码上传至Arduino

为了将开发的模型移植到目标设备上,我们将生成C代码:

In [ ]:
engee.generate_code( "$(@__DIR__)/arduino_crossroad.engee",
                     "$(@__DIR__)/arduino_crossroad_code" )
[ Info: Generated code and artifacts: /user/start/examples/codegen/arduino_crossroad/arduino_crossroad_code

在指定目录arduino_crossroad_code 中生成了头文件。此外,在示例目录arduino_crossroad 中,还提供了一个预先编写的 Arduino 程序,其名称与该目录名称相同:arduino_crossroad.ino 。 该程序中调用了代码生成时生成的头文件,初始化了全局变量,定义了模型计算循环,并调用了模型计算函数。程序的详细说明见代码注释中。

要在 Arduino 上运行该代码,需要下载目录arduino_crossroad ,并通过 Arduino IDE 将草图arduino_crossroad.ino 烧录到目标设备中。

在 Arduino 上运行代码

成功编译并将程序上传至目标设备后,调试板的数字输出端将生成用于控制交通信号灯各部分(面包板上的 LED)的信号。 为了验证模型是否正常工作,我们打开 Arduino IDE 中的串口监视器。

image_4.png

可以看到,在“To Serial ”代码块中定义的消息以要求的格式每隔 1 秒输出到串口。

按下按钮触点时,十字路口的交通信号灯将进入待机模式,同时串行端口会输出以下消息:

image_2.png

串行端口以每秒1次的频率输出所需消息。因此,交叉口在待机模式下的运行同样正常,这证实了模型和代码的可行性。

结论

在此演示示例中,为Arduino兼容平台开发了一个包含两个三区段汽车信号灯和一个两区段行人信号灯的十字路口模型。 控制算法通过有限自动机库实现。该模型可模拟交通信号灯在两种模式下的工作,不仅能实现灯区的切换,还能控制其闪烁。 该算法的运行效果已在目标设备上经过验证,生成的代码不仅能控制LED灯段,还能通过串行端口传输有关当前模式、三个交通信号灯中亮起的灯段以及当前运行时间的信息。

示例中使用的块