使用赛普拉斯的UDB PSoC控制器减少3D打印机中的中断



有关UDB专有文档翻译的评论中正确地指出,简单的事实并不能帮助您理解材料。 但是该文件恰恰包含了干燥的事实。 为了通过实践来稀释它们,让我们暂时停止翻译。 让我们把这个障碍放到手中,看看在实践中可以实现什么以及如何实现。

详细介绍


本文是构思三部曲的第二部分。 第一部分位于此处 (通过赛普拉斯UDB微控制器单元PSoC进行的RGB LED控制)。

除了在其上实现某些接口的赛普拉斯的UDB PSoC控制器之外,检查这些模块如何通过从某些资源密集型任务中卸载中央处理器,还可以使程序员的工作更加轻松,这将是很有趣的。 但是为了弄清楚我要做什么,我必须写一个广泛的序言。

在2015年秋天,我购买了一台全新的MZ3D 3D打印机,到2016年春季,我已经厌倦了它的步进电机如何运转。 时光飞逝,我们尽了最大的生存能力,所以唯一的解决办法是从微步1/16切换到1/32。 与工厂的往来表明这在Arduino上是不可能的。 事实证明,那几年的“固件”受到限制,步进频率高于10 KHz,没有采取虚拟步骤,而是采用了两个虚拟步骤,否则系统根本没有足够的时间来处理所有“步骤”中断。 只有一种方法-将所有内容拖到ARM平台上。 这是拖放操作,而不是下载操作,因为当时也没有现成的ARM解决方案。 几周后,我将所有这些转移到STM32F4,引擎的声音变得更加悦耳,问题得以解决。

然后,我们公司开始开发OS,在会议上,我不得不长时间证明,在速度方面,处理中断的典型方法并不总是可以接受的,它只适合于这种典型但非常繁琐的情况。 关于此主题的讨论已发布在我有关操作系统中断的文章中(一个俄罗斯RTOS概述,第8部分。处理中断)。 总的来说,一个问题已经解决了很长时间:频繁的辅助中断服务于一个子系统会减慢其他所有问题。 当然,对中央处理器的简单改进消除了问题,但并没有使所有人对道德的满意感得到了正确的解决。

我定期从纯粹的理论意义上回到这个问题。 例如,有一天,这种想法浮现在我的脑海,而不是使用昂贵的控制器,您可以购买三个STM32F103C8T6,其中一个现成的面包板价格为110卢布(考虑到交付),并且芯片本身甚至更便宜。 在其中之一中,仅带出了发动机控制功能。 让他将所有的计算能力都花在此功能上。 在安静的环境中,其他几个(甚至可能是一个)可以解决其他任务(处理命令,使用PWM,保持温度等)。 该解决方案还具有很大的优势-多个控制器的引脚总数非常庞大。 在一个STM32上,我不得不长时间布置纸牌,要分配给哪条腿。 尽管定时器输出的支路和ARM的ADC支路的分配比旧控制器更灵活(硬件单元的一个输出可以分配给多个物理支路之一),但是当折叠单人纸牌时,您会理解灵活性可能不够。 如果控制器很多,则选择增加。 通常,在用于步进电机的那一台上,我们只需将所有支路分配为数字输出即可。 其他人也有转身的地方。

这种方法的一个问题是如何同步这些控制器? 从理论上讲,MAX Max RTOS包含您所需的一切。 命令处理程序生成用于移动头的任务列表。 他定期修改它们(通过协调加速与新到达的任务)。 因此,应该共享塑造者和表演者的内存。 RTOS MAX包含用于组织此类共享内存的功能。 我在这里进行了描述(一个俄罗斯RTOS的概述,第7部分。在任务之间交换数据的方法)。 但是实际上,一个细微差别会破坏一切:对步进电机进行维修是一项时间紧迫的任务。 稍有延迟,我们得到了3D打印机和其他CNC机器的塑性流动-例如,螺纹螺纹不正确。 通过串行接口的任何通信都不是最快的。 加-仲裁和其他正式需要时间。 事实证明,从主处理器中删除功能的所有收益都归于开销。 当然,我利用了自己的官方立场:我去和该子系统的开发人员讨论了这个问题。 las 他们说,在OS中进行同步没有太多开销,但是对于支持相应总线的设备而言。 现在,如果我以TigerShark架构为基础,则该操作系统将为我组织所有事情而没有任何开销。 只有根据这种架构制造的控制器比我想放入的整个3D打印机贵几倍。 一般来说,再次不能接受。

我们将结束冗长的介绍。 有人会说,由于某种原因,我仍在寻找一匹白马王子。 您可以在没有操作系统的情况下完成所有工作,这里我正在考虑各种选择...您可以,但是可以,但是当出现实际问题“厌倦了听打印机的故障”时,它很快就得到了解决。 仅此而已。 她没有了。 而且,从那时起,出现了新的步进电机驱动器,它们通常以完全不同的方式解决问题(它们获得微步距1/16,并给出1/256)。 在本简介中,我精确地描述了“对于频繁中断的问题,没有完美的解决方案。” 长期以来,已经做出了一个丑陋的决定。 我不想浪费时间检查其他丑陋的决定。 他们只是在我的头上滚动。

但是,当我处理UDB块时,在我看来,可以很好地解决该问题。 您可以简单地处理从软件到固件级别的中断,而将计算部分放在主处理器的意识上。 无需其他控制器! 一切都放在同一芯片上! 因此,让我们开始吧。

真空中的球形马


在本文中,与UDB本身合作将是最重要的。 如果我说过要绑定到特定的“固件”,他们可能会正确地向我指出我误认为该集线器。 GeekTimes有什么用。 因此,UDB是主要的,而步进电机只是一个美丽的例子。 在这一部分中,我通常将在真空中制作球形马。 他将有实际的缺点,我将在第二部分中消除。 但是重复我的动作,读者将能够掌握为UDB开发固件的方法。

这样啊 步进电机控制机构如何工作? 有一项任务使磁头必须以线性速度通过的段排成一行。 到目前为止,我会假装我不记得该段开始和结束时的加速情况。 只是头部应该经过。 新的细分将放入队列的尾部。 根据来自头部的记录,一个单独的任务将STEP信号发送到所有活动的引擎。

让打印机的最大打印头速度为200毫米/秒。 假设每1毫米运动需要200步(此数字对应于具有1/32微步的实际打印机MZ3D-256C)。 然后必须为脉冲提供高达200 * 200 = 40,000 Hz = 40 KHz的频率。 以这样的频率,可以很好地调用发送步进脉冲的任务。 它必须以编程方式自行形成脉冲,还必须计算应在多长时间后调用下一个中断。

我回想起有关Kolobok和三个Bogatyrs的一个笑话,在Kolobok中,他们一直向Bogatyrs致意,然后向他们不断提问,并得到答案。 然后相继对他们说再见。 好吧,然后他遇到了三十三个骑士。 处理器扮演面包的角色,而步进马达扮演Bogatyrs的角色。 显然,在存在大量UDB块的情况下,可以将工作与引擎并行化,并将每个引擎都服务于其块。 并且由于我们有一些段,在这些段期间引擎将均匀地移动,因此,让我们尝试使设备在此类交易中而不是在每个步骤中都能正常工作。

球形马在真空中穿越线性截面需要什么信息?

  • 步骤数。
  • 步骤之间的时间段。

两个参数。 UDB只有两个电池和两个参数D0和D1的寄存器。 似乎一切都是可以实现的。 我们仅估计这些寄存器应具有的位深度。

首先,步骤数。 如果有8位数字,则在UDB操作的一个周期中,打印机将能够使笛卡尔打印机的打印头移动一点点至多1毫米(200微步)。 还不够 如果容量为16位,则步数将为65536。这是65536/200 = 327毫米。 适用于大多数型号。 对于Core,Delta和其他产品,有必要进行估算,但是总体而言-对于整个行程,该段可以分为几个部分。 不会有那么多(两个,最多三个)。

现在时期。 假设时钟频率为48 MHz。 48000000/65536 = 732。 即,使用16位分频器可获得的最小允许频率为732 Hz。 太多了 在Marlin固件中,最小值为120 Hz(大致对应于8 MHz除以相同的常数65536)。 我们必须将寄存器设置为24位。 那么最小频率将等于48000000 /(2 ^ 24)= 48000000/16777216 = 2.861 Hz。

好啊 停止无聊的理论! 让我们继续练习! 启动PSoC Creator,然后选择File-> New-> Project:



接下来,我选择了面包板,环境将从该面包板获取有关所用控制器及其设置的基本信息:



我已经准备好从头开始创建项目,因此我选择了Empty Schematic



将工作环境命名为PSoC3DTest



他在这里,已经完成了项目!



我要做的第一件事是基于UDB创建自己的组件。 因此,如上一篇文章中所述,我需要切换到“ 组件”选项卡:



右键单击项目,然后选择“ 添加组件项目”



我们说我们需要添加一个UDB文档 ,将名称更改为StepperController并单击Create New



该组件显示在树中,此外-该组件的编辑器已打开:



将数据路径块放在窗体上:



选择此块后,我们进入其属性,并将位深度从8更改为24。其余参数可以保留不变。



要同时启动所有模块(对于所有引擎),我将从外部启动启动信号(添加启动输入)。 输出:我将直接使Step退出,以便可以将其提交给步进电机驱动器以及Out_Idle 。 基于此信号,处理器将能够确定设备已完成工作的那一刻。 在图中可以看到与这些输入和输出匹配的电路的名称。



在讨论自动机的逻辑之前,我将描述另一个纯粹的工程问题:设置脉冲持续时间Step 。 DRV8825驱动程序文档要求脉冲宽度至少为1.9μs。 其他驱动程序对其宽度的要求也较低。 如理论部分已经提到的,通过设置步长和步数可以占用现有寄存器。 不管喜欢与否,应该在电路上放置一个七位计数器。 我们称其为单触发,它设置了步进脉冲。 在48 MHz的频率下,为了确保1.9μs的持续时间,该计数器应至少计数91.2步。 四舍五入到92。任何超出该值的值都将不少于。 结果是以下设置:



计数器名称SingleVibrator 。 它永远不会复位,因此“ 复位”输入总是连接到零,它会考虑机器(如下所述)处于“一个”状态时,会加载所有其他状态(起初我选择了机器的特定状态,但事实证明,使用这种棘手的方法,则需要的PLD资源要少得多,但结果是相同的)。 加载值是十进制的92。不错,好的编辑器会立即用十六进制替换此值:



当计数器计数为零时,它将向名称为One_Finished的链报告。 与柜台-就这样。

我们的机器将使用哪种状态标志? 我是这样的(我提醒您双击Datapath中的输出列表以进行设置):





在脉冲的持续时间内,我将使用电池A0作为计数器,因此当其值达到零时,将为我命名为Pulse_Finished的标志置位。 电池A1将为我计数脉冲。 因此,将其置零将使标志Process_Finished停止运行

我们构造自动机的过渡图:



设置其状态的变量称为State 。 立即将此变量映射到ALU指令的地址寄存器。 刚开始我忘记这样做,所以很长一段时间我都不明白为什么我的机器不能工作。 双击数据路径中的条目块:



并匹配:



我们开始处理过渡图和与其相关的ALU指令。

让我们从空闲状态开始。 它的行为已经相当饱和。

首先,将数据寄存器D0和D1的值分别固定放置在电池A0和A1中:



从此条目中,训练有素的眼睛将看到您需要的一切。 由于我们的眼睛仍然没有睁开,因此我们双击该条目并看到相同的内容,但是会更加详细:



此处的主要值是填充电池A1(脉冲计数器)。 当程序输入值D1时,它将立即转到A1。 在下一个措施之前,该程序肯定没有时间开始该过程。 检查该值以形成退出该状态的条件,也就是说,没有其他地方可以填充它。

现在,让我们看看在过渡图级别执行的操作:



辅助触发器Start_Prev允许在输入Start处捕捉上升沿 ,并组织1个周期的延迟线。 它始终包含上一个小节中“ 开始”输入的状态。 有人更熟悉在Verilog中看到的内容:



相同文字
always @ (posedge clock) begin : Idle_state_logic case(State) Idle : begin Start_Prev <= (Start); IsIdle <= (1); if (( Start&(!Start_Prev)&(!Process_Finished) ) == 1'b1) begin State <= One ; end end 


因此,仅当小节之间出现起始线差异起始&(!Start_Prev)条件才成立。

此外,当计算机处于此状态时, IsIdle输出进入单个状态,从而通知外部环境该块是被动的。 通过这种方法,与将State == Idle构造提交到输出相比,所花费的PLD资源更少。

启动信号的差异来自外部环境,并且累加器A1的值非零时,机器将退出空闲状态。 如果在A1中输入零,则该段的开发将不涉及引擎,因此忽略了起始行上的差异。 这适用于未使用的挤出机。 对于某些打印机,也很少使用Z轴引擎。让我提醒您,如何形成一个条件来显示A1中的零值(并且非零是其反转):



接下来,机器进入状态One



在这种状态下, Step输出设置为1。向驱动器施加一个step脉冲。 另外, IsIdle触发器的值被重置 。 外部环境被告知该设备处于活动阶段。

One_Finished信号退出该状态,当七位计数器计数为零时,该信号将提高为1。 让我提醒您,此特定计数器生成One_Finished信号:



当机器处于此状态时,ALU将寄存器D0中的值加载到电池A0中(设置脉冲持续时间)。 让我仅向您显示一条简短的说明:



加载的值将在以下状态下使用。 在其中,机器产生一个延迟,该延迟设置脉冲持续时间:



步进输出重置为零。 电池A0减少,如以下简短输入所示:



如果双击它-一个完整的条目:



当A0的值达到零时,将增加Pules_Finished标志,并且机器将进入减量状态:



在这种状态下,在ALU中,累加器A1的值减小,从而设置脉冲数:



完整记录版本:



根据结果​​,转换到下一个脉冲或空闲状态。 双击状态以查看转换,其中考虑了优先级:



实际上,有了UDB,一切都变得如此。 现在我们制作相应的符号。 为此,右键单击编辑器,然后选择Generate Symbol



我们转到项目图:



并且我们介绍一种电路,其中有一定数量的这些控制器。 我选择了五个(三个轴加两个挤出机)。 具有大量挤出机的打印机不会被认为是便宜的。 您可以将FPGA放在它们上。 一路上,为了看到真正的复杂性,我扔了一个USB-UART模块(用于从计算机或同一Raspberry Pi接收数据)和一个真正的UART(它将与便宜的Wi-Fi模块ESP8266或一个可以通过UART发送GCODE)。 我没有添加PWM等,因为它们的复杂性很明显,而且实际系统还很遥远。 原来是这样的:



控制寄存器产生触发信号,该信号同时进入所有模块。 另外,让信号从中出来,在段的形成过程中这些信号是静态的。 我通过“ And”收集了所有空闲输出,并应用于中断输入。 我在积极的方面任命了一名中断人员。 如果至少有一个引擎启动,则中断输入将被重置。 在最后一个引擎结束时,它将被启动,这将通知处理器有关下一段结束的准备情况。 现在,通过双击Clocks树元素来调整频率:



在出现的表中,双击PLL_OUT元素:



我们会以某种方式填写表格(我还不太了解设置此表格的规则,这就是为什么我使用术语“类似的东西”)的原因:



现在双击Clock_1行:



将UDB模块的时钟频率设置为48 MHz:



由于该项目是实验性的,因此没有必要为其创建API。 但是,为了巩固上一篇文章中研究的内容,我们再次转到“组件”选项卡,并在StepperController项目中,右键单击“添加组件项”,首先添加头文件,然后添加C源代码文件:





我将简要介绍一下我添加的细分的初始化和开始这两个功能。 其余的可以在本文的示例中看到。

 void `$INSTANCE_NAME`_Start() { `$INSTANCE_NAME`_SingleVibrator_Start(); //"One" Generator start } void `$INSTANCE_NAME`_PrepareStep(int nSteps,int duration) { CY_SET_XTND_REG24(`$INSTANCE_NAME`_Datapath_1_D0_PTR, duration>92?duration-92:0); CY_SET_XTND_REG24(`$INSTANCE_NAME`_Datapath_1_D1_PTR, nSteps>1?nSteps-1:0); } 

我用main.cpp替换了main.c的名称,以验证开发环境将正常响应C ++,因为Marlin固件是面向对象的。 可以预见的突发错误,可以通过添加常规内容来消除:



相同文字
 extern "C" { #include "project.h" } 


对于引擎的全球发布,我做了这样的功能(这很粗糙,但是对于在真空中使用球形马的实验来说,它将起作用,在实验中,开发时间比美感更重要):
 void StartSteppers() { Stepper_Control_Reg_Write (1); Stepper_Control_Reg_Write (1); Stepper_Control_Reg_Write (1); Stepper_Control_Reg_Write (0); } 

她发出启动信号,以防万一,立即进行三个小节,然后再次下降。

好,让我们开始实验。 首先,仅跨过X和Y引擎(在示例中,第一组调用将初始化所有控制器,第二组调用将X和Y控制器设置为所需的步数并开始该过程):

 int main(void) { CyGlobalIntEnable; /* Enable global interrupts. */ StepperController_X_Start(); StepperController_Y_Start(); StepperController_Z_Start(); StepperController_E0_Start(); StepperController_E1_Start(); StepperController_X_PrepareStep (10,1000); //    StepperController_Y_PrepareStep (50,500); StartSteppers(); //   for(;;) { } } 

我们看一下结果:



检查正脉冲的持续时间:



没错 最后,我们检查中断的工作情况。 添加一个全局计数器变量:

 static int nStep=0; 

该变量在函数中分配给一个,并在中断处理程序函数中增大。 仅出于验证目的,中断处理程序将仅触发一次。 我是这样的:

 extern "C" { CY_ISR(StepperFinished) { if (nStep == 1) { StepperController_X_PrepareStep (5,500); StartSteppers(); nStep += 1; } } } 

函数中,我实际上添加了两行:包含中断和此非常变量的分配。 当机器启动时,我已经分配了。 否则,将发出错误的中断请求。 现在没有特别的理由要与之抗争。 该项目是一个实验性项目。



相同文字
 int main(void) { CyGlobalIntEnable; /* Enable global interrupts. */ isr_1_StartEx(StepperFinished); StepperController_X_Start(); StepperController_Y_Start(); StepperController_Z_Start(); StepperController_E0_Start(); StepperController_E1_Start(); /* Place your initialization/startup code here (eg MyInst_Start()) */ StepperController_X_PrepareStep (10,1000); StepperController_Y_PrepareStep (20,500); StartSteppers(); nStep = 1; for(;;) { } } 


我们检查结果(在第二步中,只有引擎X应该可以工作,并且步数应该变成一半):



没错

结论


通常,很明显,UDB块不仅可以用于设置快速硬件功能,还可以用于将逻辑从软件转移到固件级别。 不幸的是,文章的篇幅如此之大,以致于无法完成审查并无法明确地回答UDB功能是否足以完成任务的最终解决方案。 到目前为止,只有球形马在真空中准备就绪,其动作在原理上与所需的动作非常相似,但是熟悉步进电机控制理论的恼人读者会发现它有很多缺点。 所提供的单元不支持加速,否则加速度将无法运行。 而是,它支持,但是在此阶段将需要较高的中断率,并且为了避免这种情况,人们设想了所有方法。

设置所呈现的块的频率的准确性是远远不能接受的。 特别是,它将以1200分频器提供40,000 Hz的脉冲频率,并以1201分频器提供39966 Hz的脉冲频率。在本机上这两个值之间的中频是无法实现的。

也许其中还有其他缺点。 但是我们将在下一篇文章中处理它们,以检查是否有足够的UDB资源。

同时,除其他外,读者还获得了一个从头开始基于UDB创建块的真实示例。 本文撰写期间获得的测试项目可以在此处进行

Source: https://habr.com/ru/post/zh-CN433800/


All Articles