在这个实验室中,我们使用FPGA来使用激光和一组称为电流计的电动机控制镜(" Galvos&#34)的一组电动机控制镜子。实现这一点要求我们足够快地移动电极,使得激光器追踪的路径出现为人类观察者产生图像。具有其巨大信号处理和I / O功能的FPGA非常适合该高速控制任务。
虽然控制伽罗斯是FPGA的一个很好的任务,但是从存储器读取矢量图像文件并将其解析成可以容易地绘制的形式是在微控制器上更好地完成的复杂顺序问题。除FPGA外,我们还使用的芯片板上还包括称为HPS或硬处理器系统的臂基微控制器。我们在这个系统上运行了嵌入式Linux发行版,为我们提供了一个清洁的操作系统环境来运行代码。使用此HPS,我们能够包含C组件,该组件是所有预处理工作,然后将路径转发到FPGA以绘制。
电流计是基于施加的电压旋转到特定位置的装置。我们使用了两个带有附着镜的镀锌,以控制激光束的路径。考虑图1,其示出了一组镀锌的操作。设置包括沿着Y轴以直角保持的两个镜子,其围绕X(对于Y Galvo)和由附加的电池控制的Z(用于X Galvo)轴的旋转。激光首先撞击X Galvo,然后在离开装置之前跳出Y伽罗。 X Galvo的旋转控制激光投射位置的X位置,Y Galvo的旋转类似地控制Y位置。通过使用连接的电动机调节镜子,控制器可以高速移动激光束的投影。
考虑图2,显示了我们系统的整体结构。为了使用我们的伽迹绘制矢量图像文件,我们首先通过它通过在HPS上运行的C代码传递它,该C代码从内存中读取文件,解析它,并将其传递给FPGA。然后,FPGA沿着矢量路径插入一系列位置,并使用一对DAC将这些位置作为模拟信号发送到伽罗醇。它还使用通用I / O引脚将数字开/关信号发送到激光器。这些信号通过运行电机和激光的电动驱动器电路。最后,物理系统通过用激光绘制图像来响应电信号
SVG(可扩展矢量图形)规范描述了一种用于编码向量图形文件的有效文件格式。为了能够显示以这种格式创建的许多现有的矢量图像,我们选择使用SVG标准作为我们项目的基础。
SVG格式基于XML的结构(可扩展标记语言)。也就是说,SVG文件由树数据结构组成,其中每个节点以< nodename&gt开头;标记和以< / nodename&gt结尾;标签。括在节点的打开和结束标签之间的节点是其儿童。节点还可以携带以表单中编码的属性< nodename attributeName =“attributevalue”> 。 SVG标准描述了一种可以在该树数据结构及其含义中使用的特定XML标记集。
虽然SVG标准相当大,但我们项目的相关部件是< path>元素。这是用于描述实际矢量路径本身的元素。路径数据以与G代码类似的样式编码为字符串列表命令。每个命令都包含一个字母,以识别它所需的数字值。此字符串存储为路径元素的d属性。表1显示了可用的命令及其编码。
路径总是从绝对移动到定位所有未来坐标。从那里,命令定义一系列路径段,可以是线,二次曲线,立方曲线或椭圆曲线。该规范还包括用于常用的段类型 - 水平线,垂直线和平滑Bezier曲线(自动推断为第一控制点)的简写形式。 Close Path命令生成返回子路径的开始。
由于SVG文件基于极其常见的XML格式,因此有许多开源库可以从内存中读取它们并将它们解析为树数据结构。由于其清洁接口,允许麻省理工学院许可证和可靠性的一般声誉,我们选择使用Libxml2库。
使用libxml2解析SVG文件后,我们的下一个任务是提取我们所需的信息并丢弃其余的信息。我们首先从根节点的属性中拔出宽度和高度。然后我们走了树寻找任何路径元素,所以我们可以提取他们的路径数据。
不幸的是,由于SVG路径刚刚被编码为字符串,我们仍然需要通过另一个解析器将路径数据拔出,以将其转换为可用的表单。为了实现此目的,我们使用Flex Lexer生成器和Bison Parser发生器写了一个小解析器。此解析器扫描路径字符串并扫除了链接的段命令列表。解析文件中的所有路径后,我们将这些列表连接在一起以创建单个大路径。
在将此路径发送到FPGA之前,我们将其通过预处理的几次通过,这有助于降低我们的FPGA代码的复杂性。我们应用了以下转换
扩展和抵消转换是对我们硬件现实的特许权。我们使用了8位DAC输出每个轴的模拟信号,因此我们只有255个位置。此外,由于镜子对齐的困难,我们不愿意一直到达盖尔沃斯的最远的位置。因此,我们选择在规模的两端刮掉20个位置屏障。这导致转变为范围[20,235]
由于可以将椭圆弧转换为相同系列的立方Bezier曲线,因此避免在FPGA上实现椭圆弧并以软件处理它们。但是,我们在项目结束时没时间了,而且从未到过实际实施此功能。
QSYS是一个专有的Altera互连,处理基础设施,以允许HP与FPGA通信。将模块放在总线上并将其分配地址的工具内置于Altera开发环境中。为了与我们的C代码与FPGA交谈,我们在Qsys总线上使用了轻量级主人。我们首先记忆映射了Master的物理基地址,进入了C代码的虚拟地址空间。然后,我们为Qsys总线上的每个组件添加了适当的地址偏移,以计算我们可以使用的指针与它们交谈。图3显示了QSys总线的地址映射。
为了控制FPGA上的求解器,我们使用并行端口以使HPS访问复位线和使能线。保持重置线完全重置系统,同时保持使能线路低导致FPGA完成绘制图像,然后停止而不是无限制地重复图像。当FPGA达到此最终状态时,我们使用从FPGA运行的另一个并行端口从FPGA运行到HPS到我们的C代码。
此启用/完成界面允许我们在加载新建之前清洁完成绘制图像。由于所有图像路径在中心位置开始并结束,因此在GALVO位置中没有不连续的跳跃,平滑其行程路径。由于盖尔沃斯是脆弱的,并且对尖峰或跳跃不响应,这是一个理想的财产。
除了并行端口接口外,我们还使用了一系列RAM块来保存路径数据。这些由PATH_TYPE RAM块组成,该RAM块保持用于段类型的编码,以及一组X0,X1,X2,X3,Y0,Y1,Y2,Y3 RAM块,其保持每个段的控制点。给定块中的第n个条目与所有其他的第n个条目相对应。由于不是所有命令类型使用了所有四个控制点,因此我们刚刚离开了未使用的条目空白。我们还将特殊的终端命令作为RAM块中的最终条目中包含,信号传达到状态机,即它应该返回开始并重复图像路径。
使投影图像旋转是一种涉及田间和余弦的复杂转换。由于这种旋转在较慢的时间质上运行比用于控制伽利差的高速模拟信号,因此我们选择在HPS上进行数学,并定期向FPGA发送新帧。我们发现,慢速速度(>每旋转3秒)与每秒10帧的更新速率相结合,产生相对平滑的运动。
我们选择在签署的两个' s补充10.18固定点格式中做我们所有的数学。由于我们的输出需要是无符号的8位数,因此可以选择10个整数比特的选择以方便地检测溢出(第二位上位)和下溢(底部的两个比特都被设置)。 18分数允许我们沿着高精度的路径插入。
虽然年轻的纯粹数学领域甚至是Millenia的纯粹数学领域,但是当今计算机图形已经是超过60年的兴趣领域。这意味着几乎所有直接的图形任务都具有经典且熟悉的算法来执行它们。其中是Bresenham的线算法,它提供了一种优雅的方法,用于在像素网格上绘制两点之间的线。
Bresenham的线路算法将此任务分为两种情况 - 线斜率的大小小于或等于一个,并且另一个斜率的大小大于或等于一个。我们首先考虑第一次案例。
Plotlinelow(x0,y0,x1,y1)dx = x1-x0 dy = y1 - y0 yi = 1如果dy< 0 yi = -1 dy = -dy结束如果d = 2 * dy - dx y = y0 for x0到x1绘图(x,y)如果d> 0 y = y + yi d = d - 2 * dx结束如果d = d + 2 * dy
清单1:Bresenham的斜率线算法小于或等于1 [2]
考虑列表1,显示该算法的伪代码。通过循环的每次迭代我们递增x和更新d。每当D超过0时,我们也会递增y。这允许我们沿着像素沿着线像素踩踏。
PlotlineHigh(x0,y0,x1,y1)dx = x1-x0 dy = y1 - y0xi = 1,如果dx< 0 xi = -1 dx = -dx结束如果d = 2 * dx-dy x = x0为y从y0到y1 plot(x,y)如果d> 0 x = x + xi d = d - 2 * dt结束如果d = d + 2 * dx
清单2:Bresenham的斜坡线算法大于或等于1 [3]
现在考虑清单2,显示斜坡大于1的伪代码。该算法对第一种情况完全对称:当D超过0时,我们递增y。使用Bresenham的算法仅需要决定应用哪种情况,然后将其委派给相关的子程序。
要允许我们插入线路和移动SVG路径命令的点,我们在FPGA上的硬件上建立了Bresenham的线路算法。我们设计了实现Linelow和Linehigh的模块,然后将它们包裹在一个更高级别的模块中,该模块将两个混合在一起,以便将相应的模块发送到输出。
图4显示了Linelow模块的状态机。系统最初在状态0等待有效输入。当in_val行变为高电平时,它会转换为状态1并将所有值从输入线复制到寄存器中。在下一个周期上,它初始化x,y和d并转换到状态2,在其开始运行循环。每次通过循环时,它会递增x,更新d,如果d&gt,则递增y; 0。然后它转换为陈述3,其中它提出Out_Val并等待Out_rdy行高,表明另一侧已准备好接受该事务。发生这种情况时,它会返回到状态2并计算下一个点。当x最终到达结束时,它会转回状态0并完成完成,发出已完成该行的顶级模块。然后它等待IN_VAL再次高举,此时它开始计算新行。
LineHigh的实现与x和y交换的角色完全对称。
为了保护输出从过度和下溢,包装模块检查X和Y的顶部两个位。如果设置了顶部位,则指示下溢,则返回0。如果第二位但不是顶部位设置,则指示溢出,则返回255。这可以防止溢出错误传播到输出。
与具有不需要乘法的简单像素插值算法不同的线路,实现了二次曲线的BERESENHAM线路算法非常复杂。因此,我们决定在其控制点方面沿着二次Bézier曲线的简单参数化。
图5示出了实现该等式的电路的框图。我们首先计算,和。然后,我们将每个术语乘以每个控制点的和输入,并将它们放在一起以创建和输出。第二项的乘法乘两个是使用左移实现的。在向我们的结果发送输出之前,我们通过我们用于线路的相同溢出保护电路。该设计导致电路的两个乘法输出延迟。
沿着参数化方程插入这样的参数化程创造了一些问题。根据我们的步骤尺寸,我们可能会跳过一些像素并在伽迹位置创建不连续的跳跃。或者,输出图像中的一些像素可以与其他像素具有比其它更高的内插点别名。由于我们物理设置的性质,这将导致激光在那些像素处花费更多时间,因此这些位置看起来比邻居更亮。
我们通过选择小型步长来处理第一个问题(我们选择了1/512,这对于我们的255 x 255输出网格绰绰有余)。为了处理第二种我们在我们的状态机中建立逻辑,只有在它们别名更改的像素时才输出点。这导致它采用变量和可能大量的循环来计算曲线中的下一个点。但是,由于我们的求解器在比用于换流输出的时钟更快的时钟上运行,因此这种不规则的生产率导致任何问题都没有问题,我们总是在我们需要时准备好点。
图6显示了我们的二次Bézier插值模块的状态机。我们使用与线路模块相同的界面以进行通信统一。最初我们处于状态0等待有效输入。当IN_VAL变高时,我们将我们的输入复制到我们的寄存器并转换到状态1.在状态1中,我们增加。我们将插值电路的输出与Last_x进行比较,而Last_y存储我们发送到输出的最后一个像素的值以确定像素是否已更改。如果像素已更改,或者如果它是第一点,则我们向状态2前进,请突出,并等待顶级模块通过升级out_rdy接受我们的输出。当发生这种情况时,我们完成事务并返回状态1以继续插值。如果像素没有改变,那么我们仍然存在于状态1.最终达到1我们返回状态0并完成完成,告诉我们已完成曲线的顶级模块。
像二元斑驳曲线一样,立方Bézier曲线没有简单的Bresenham风格像素插值算法。因此,我们再次选择以其参数化表单踩到0到1来实现它们。
虽然二次Bézier曲线的参数化形式是加权线的加权之和,则立方Bézier曲线的参数化形式是二次浮屏曲线的加权和。这方便我们允许我们从二次Bézier插值模型中重复使用计算代码。
图7显示了实现立方插值的逻辑的框图。我们在两个二次围流曲线的曲线上采取和输出通过适当的重量将它们中的每一个乘以,在总结它们以创建输出之前。这增加了序列的额外乘法,为整个电路创建了三个乘法等待时间。
立方Bézier插值模块使用相同的接口,并具有与二次Bézier插值模块相同的状态机。唯一的区别是它使用更复杂的电路来插入新值。
在创建模块以沿三个不同的路径段类型内插对像素后,我们创建了一个顶级求解器模块,以从RAM读取命令并将它们分配给相应的内插器。我们创建了9个单独的RAM块 - 一个用于段类型,八个持有四个控制点的x和y坐标。每个RAM中的第n个条目对应于每个其他RAM中的第n个条目,这意味着不需要所有四个控制点的命令只是留在一些RAM中的左孔。
图8显示了顶级求解器的状态机。我们从状态0开始,我们并行发出读取九个RAM块。我们向第1州提升到状态2,此时读取的结果已准备就绪,我们将RAM的输出转移到我们的寄存器中并继续到州3.我们然后根据所内容调度命令类型寄存器。如果是移动命令,我们将激光关闭并将控制点发送到线路模块,而对于线命令我们使用线路模块但保持激光器。对于二次Bézier曲线,我们使用二次曲线模块和CubicBézier曲线,我们使用立方曲线模块。在这两种情况下,激光仍然存在。对于所有这些命令,我们前进到状态4.如果代替类型寄存器保存特殊的结束命令,则我们已到达路径的末尾。如果启用了求解器,则我们将读取地址重置为0并返回状态0以再次从开头开始路径。如果不是,则我们移动到状态7,请抬起DONEPTALLPATINAL端口,并再次等待求解器,以便我们返回状态0。
在状态4中,我们在in_val行中降低并继续前进到状态5,我们等待输出FIFO中的空间,并为out_val或相关插值者的完成行被提出。如果已完成完成,则我们将读取地址递增并返回到状态0以读取RAM中的下一个命令。另一方面,如果提出OUT_VAL然后我们将OUT_RDY提升到完成交易,将点和激光器的状态写入输出FIFO,然后继续前进6.最后,在状态6中,我们完成FIFO写入并返回州5等待下一个点或完成细分。
我们使用的廉价伽利略普罗斯无法准确响应,以便在我们的解算器运行的高速控制信号时控制信号。为了在我们消耗点的速率和我们生产的速率之间处理这种不匹配,我们使用了一组FIFO队列 - 一个用于X通道,一个用于Y通道,一个用于激光的状态。这些队列的写侧位于求解器时钟,求解器仅在FIFO中有足够的空间存储它们时产生点。队列的读取侧是在较慢的时钟,驱动伽罗斯。
为了
......