使用数据路径配置工具



在与UDB合作的实际开发中,我们必须采取倒数第二个步骤。 今天,我们将不使用自动UDB编辑器进行开发,而是使用数据路径配置工具以半手动模式进行开发。 掌握该工具的一个很好的帮助是AN82156-PSoC 3,PSoC 4和PSoC 5LP-使用UDB数据路径设计PSoC Creator组件。 实际上,我自己研究过。

也许,在阅读我们有关UDB的文档翻译时 ,有人试图将其中的知识复制到实践中,并注意到并非UDB编辑器中提供了出版物中描述的所有功能。 这是由于开发人员没有开始在UDB编辑器中放置一些特别麻烦的机制。 AN82156的作者认为,通过UDB编辑器不能执行以下操作:

  • 组织并行数据输入和输出;
  • 组织动态FIFO管理;
  • 实现FIFO时钟信号的反相;
  • 实现CRC功能;
  • 实施PRS功能;
  • 实施入库转移的选择;
  • 实施动态的入站迁移。

从我自己身上,我将补充一点,就是我没有找到如何在UDB编辑器中实现半字节置换。

如果项目中需要这些功能,则需要创建自己的Verilog代码。 我专门使用“创建”一词,而不是“写入”一词。 在阅读方面,了解这种编程语言就足够了。 我的意思是,您需要了解什么需要什么设计。 能够从头开始写总是有用的,但是本文介绍的内容不需要此技能。

作为一个可解决的问题,我选择了半合成的情况。 总的来说,我决定将一些数据输出到并行端口,尤其是从当前的状态来看,文本LCD具有并行端口。 三年前,当我将MZ3D 3D打印机移植到STM32时,我将其从MZ3D 3D打印机中取出。 因此,情况也是半合成的:如今,此类指示器通常具有I2C输入,并且在现实生活中无需通过一堆电线进行连接。 但是,现代LCD也具有并行端口,因此每个人都可以使用它们来重复实验。

考虑一下来自reprap.org的显示切换方案(这并不容易,我的提供商阻止了该站点,以及许多其他技术站点,以使其与被阻止者位于同一IP上这一事实为动机)。



很棒的布局! 首先,我不必考虑读取:LCD中的数据只能写入(R / W线接地并且在连接器上不可用)。 其次,数据采用4位格式,这意味着我们不仅可以计算出并行输出,还可以检查半字节置换功能的操作。

项目创建


因此,启动PSoC Creator,然后选择File-> New-> Project



接下来,我选择面包板:



接下来是空图:



我将这个项目称为LCDTest2



现在,和以前一样,转到“ 组件”选项卡:



并且,选择项目后,按鼠标右键,然后选择“ 添加组件项目”



在这里,您必须选择“ 符号向导” 。 给个名字...好吧,假设LCD4bit



我将以下端口分配给该符号:



clk是时钟输入。 带LCD前缀的端口是标准LCD端口。 饥饿的输出告诉DMA单元FIFO中有可用空间,有关控制RGB LED的文章中讨论了这种想法。 单击确定以获取字符。



现在,基于此符号,应生成一个Verilog模板。 单击符号附近的鼠标右键,然后在上下文菜单中选择“ 生成Verilog ”。



我们得到了下图所示的模板(以文本形式,它没有任何意义):



我们创建了一个模块和一些部分。 但是他们还没有创建数据路径。 要添加它,请转到项目树,选择LCD4bit.v文件,按鼠标右键,然后在出现的上下文菜单中选择“数据路径配置工具 ”:



我们面前会打开一个窗口,目前我仅会部分显示:



请喜欢和喜欢,数据路径编辑器。 它包含专有文档翻译中描述的所有位。 但是这些东西太多了,以至于在初期我看着他,却害怕做任何事情。 看,看,然后出去。 一段时间之后,他习惯了,开始尝试做一些事情。 实际上,这就是为什么我只带了一部分窗户的原因。 为什么要提前吓到所有人? 同时,我们只需要创建一个数据路径,所以我们选择菜单项Edit-> New Datapath



在出现的对话框中选择哪个选项?



这个问题比看起来要严重得多。 让我什至突出显示下一段,这样就不会有人被抓住(我被自己抓住,然后在网络上看到我所提出的问题,而没有人真正回答过,答案在AN82156中 ,您只需要对角阅读即可,就像上面所说的那样)简短的短语)。
如果计划使用并行数据,则必须肯定选择选项CY_PSOC3_DP。 没有其他选项将包含用于连接并行数据的端口。
这样啊 将该实例称为LCD_DP:



单击“确定”并暂时关闭“数据路径配置工具” ,并同意保存结果。 稍后我们会回到这里。

我们的Verilog代码已扩展。 现在它具有数据路径。 他的开始是完全不可读的。 它并不可怕,它是由Datapath Config Tool配置的



我们将统治数据路径描述的结尾。 我们的网站看起来像这样
(从这一点出发,将所有内容都以文本形式显示是有意义的)。
)) LCD_DP( /* input */ .reset(1'b0), /* input */ .clk(1'b0), /* input [02:00] */ .cs_addr(3'b0), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0), /* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(), /* output */ .f0_blk_stat(), /* output */ .f1_bus_stat(), /* output */ .f1_blk_stat(), /* input */ .ci(1'b0), // Carry in from previous stage /* output */ .co(), // Carry out to next stage /* input */ .sir(1'b0), // Shift in from right side /* output */ .sor(), // Shift out to right side /* input */ .sil(1'b0), // Shift in from left side /* output */ .sol(), // Shift out to left side /* input */ .msbi(1'b0), // MSB chain in /* output */ .msbo(), // MSB chain out /* input [01:00] */ .cei(2'b0), // Compare equal in from prev stage /* output [01:00] */ .ceo(), // Compare equal out to next stage /* input [01:00] */ .cli(2'b0), // Compare less than in from prv stage /* output [01:00] */ .clo(), // Compare less than out to next stage /* input [01:00] */ .zi(2'b0), // Zero detect in from previous stage /* output [01:00] */ .zo(), // Zero detect out to next stage /* input [01:00] */ .fi(2'b0), // 0xFF detect in from previous stage /* output [01:00] */ .fo(), // 0xFF detect out to next stage /* input [01:00] */ .capi(2'b0), // Software capture from previous stage /* output [01:00] */ .capo(), // Software capture to next stage /* input */ .cfbi(1'b0), // CRC Feedback in from previous stage /* output */ .cfbo(), // CRC Feedback out to next stage /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po() // Parallel data port ); 


吓人的 现在,我们要弄清楚是什么-它将不再令人恐惧。 实际上,本文分为三个不同的组。 让我们回想一下文档的翻译。 图片中的数据路径是什么样的? 我将立即在图中指出“ 1”,“ 2”和“ 3”组所属的位置。



实际上,verilog代码中的第一组端口是输入。 比较输入多路复用器输出处的名称(图中的“ 1”)和代码中的信号名称。

现在所有输入均为零。 我们将必须连接时钟输入,并且可以转发多达6条输入线,就像在UDB编辑器中所做的那样。 这些输入是:

  /* input */ .reset(1'b0), /* input */ .clk(1'b0), /* input [02:00] */ .cs_addr(3'b0), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0), 

第二组是出口。 代码中的名称也与输出多路复用器“ 2”的输入名称一致:

  /* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(), /* output */ .f0_blk_stat(), /* output */ .f1_bus_stat(), /* output */ .f1_blk_stat(), 

只有给定的数据路径种类具有第三组(其他种类均没有,因此没有并行数据)。 这些是内部数据路径信号,您可以通过这些信号独立地链接在一起或执行其他有用的操作。 代码中的名称也与图中散布的内部信号的名称一致。 我们通过其中之一(列表的最后一个,他的名字叫po )将并行数据直接输出到芯片的分支。

  /* input */ .ci(1'b0), // Carry in from previous stage /* output */ .co(), // Carry out to next stage /* input */ .sir(1'b0), // Shift in from right side /* output */ .sor(), // Shift out to right side /* input */ .sil(1'b0), // Shift in from left side /* output */ .sol(), // Shift out to left side /* input */ .msbi(1'b0), // MSB chain in /* output */ .msbo(), // MSB chain out /* input [01:00] */ .cei(2'b0), // Compare equal in from prev stage /* output [01:00] */ .ceo(), // Compare equal out to next stage /* input [01:00] */ .cli(2'b0), // Compare less than in from prv stage /* output [01:00] */ .clo(), // Compare less than out to next stage /* input [01:00] */ .zi(2'b0), // Zero detect in from previous stage /* output [01:00] */ .zo(), // Zero detect out to next stage /* input [01:00] */ .fi(2'b0), // 0xFF detect in from previous stage /* output [01:00] */ .fo(), // 0xFF detect out to next stage /* input [01:00] */ .capi(2'b0), // Software capture from previous stage /* output [01:00] */ .capo(), // Software capture to next stage /* input */ .cfbi(1'b0), // CRC Feedback in from previous stage /* output */ .cfbo(), // CRC Feedback out to next stage /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po() // Parallel data port ); 

这样啊 在工作时,我们将必须将其中一些输入和输出连接到我们自己的实体,其余的-只需将它们保留为创建它们的形式即可。

使用UDB编辑器作为参考


现在我们有一个空白,我们知道必须在哪里写什么。 仍然需要了解我们将要输入的确切内容。 碰巧我不是每天都使用Verilog语言,因此总的来说,我会记住所有事情,从头开始写对我来说总是很紧张的情况。 当项目已经在进行中时,一切都会被记住,但是如果经过几个月的不活动之后,我当然会从头开始,那么,我将不再记得这种特定语言的语法细节。 因此,我建议要求开发环境为我们提供帮助。

用于自我监控的UDB编辑器可构建Verilog代码。 我们利用了这样一个事实,即未编译主电路中不涉及的组件,因此我们可以在UDB编辑器中创建一个辅助组件,并且该辅助组件不会进入输出代码。 我们将在此处绘制一个自动机,我们将对Datapath的输入和输出进行粗略的调整,然后将自动生成的文本传输到我们的verilog模块并创造性地进行修改。 这比记住Verilog语法细节并从头开始编写所有内容要简单得多(尽管无论谁不断使用Verilog,当然,从头开始编写都会更容易:创造性的完成(很容易看到)很简单,但是需要时间)。

因此,我们开始制作辅助组件。 通过通常的移动,我们向项目添加了一个新元素:



这将是一个UDB文档,我们称之为UDBhelper



是时候考虑机器了,我们将其放置在创建的图纸上。 为此,我们需要考虑应该使用什么时序图:





这样啊 首先,您需要设置RS信号(因为R / W在硬件中被焊接为零)。 接下来,等待tAS,然后升高信号E并设置数据(相对于上升沿E的数据设置不受限制)。 总线上的数据必须不小于tDSW,然后必须丢弃信号E,数据必须在总线上至少保留tDHW,至少保留RS至少tAH。

RS是命令或数据标志。 如果RS为零,则写入命令;如果为1,则写入数据。

我建议通过FIFO0发送命令,并通过FIFO1发送数据。 在当前任务的框架内,这并不矛盾。 然后我提出的有限状态机将具有以下形式:



空闲状态下,机器仍然没有FIFO数据。 如果数据出现在FIFO0中 ,它将进入LoadF0 ,将来它将在此从FIFO0接收数据到A0。

在发送命令时,不应发送数据。 因此,用于接收数据的条件将优先于用于接收命令的条件。



数据以状态LoadF1在A1中接收(它们只能从FIFO1进入寄存器A1,而不能进入寄存器A0),然后在状态A1toA0中将它们从A1复制到A0。

无论我们以何种方式到达箭头的收敛点,我们都将数据存储在A0中。 它们已经输出到并行端口。 我们翘起E(在状态E_UP1 ),放下E(在状态E_DOWN1 )。 接下来,我们将具有交换半字节( SWAP )的状态,此后E再次上升( E_UP2 )。 在此基础上,我用尽了可以用三位编码的八个状态。 而且我们还记得Datapath动态配置RAM只有三个地址输入。 可以应用一些技巧,但本文已经很大。 因此,仅第二次我们将E置于空闲状态。 那么八个州对我们来说已经足够了。

我们还将Datapath放在工作表上,并以前面文章中熟悉的方式分配其输入和输出。 以下是输入:



以下是输出:



没什么新鲜的,一切都已经在本周期的先前文章中进行了描述。 因此,我们有一个空白,我们可以在此基础上做一些自己的事情。 没错,为了确保一切正常,我们需要将系统带到项目的顶层,否则不会发现错误。 并且在最初的实验中没有错误将无法正常工作。 因此,我们将采取另一项辅助行动。

关于如何制作电路的描述超出了使用UDB的描述。 我只告诉你我走哪条电路。 仅有一个DMA单元:向LCD发送命令时,必须承受较大的停顿,因此以编程方式进行此操作仍然更加容易。 对于其他应用程序,您可以简单地使用hungry0信号以类推方式放置第二个DMA块。



为了准确地满足时间范围,我选择了一个等于1兆赫兹的时钟频率。 可能会采用更高的频率,但是数据会在干扰较大的情况下通过长导线传输,因此最好花一些时间在门控前后设置数据,并留有余量。 如果有人会在同一面包板上重复我的实验-请勿使用端口P3.2:电容器会焊接到板上的该脚上。 我杀了半个小时,直到发现为什么我没有形成脉冲E,我才在那儿建立了联系。 我把它扔到了P3.1-一切都立即生效。 我的数据总线转到P3.7-P3.4,RS转到P3.3,所以E最初转到P3.2 ...

好吧 现在,如果您尝试编译项目,我们将得到完全可预测的错误



因此系统正在尝试收集一些东西。 但是她仍然没有什么可收集的。 我们继续复制代码。 为此,在UDB编辑器中,切换到“ Verilog”选项卡(此选项卡位于“ UDB编辑器”工作表的窗口下方):



那里熟悉什么? 文本的最后是自动机的主体。 让我们从中开始迁移。

还要将其放在数据路径下:
 /* ==================== State Machine: SM ==================== */ always @ (posedge clock) begin : Idle_state_logic case(SM) Idle : begin if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; end else if (( !F1empty ) == 1'b1) begin SM <= LoadF1 ; end end LoadF0 : begin if (( 1'b1 ) == 1'b1) begin SM <= E_Up1 ; end end E_Up1 : begin if (( 1'b1 ) == 1'b1) begin SM <= E_Down1 ; end end E_Down1 : begin if (( 1'b1 ) == 1'b1) begin SM <= SWAP ; end end SWAP : begin if (( 1'b1 ) == 1'b1) begin SM <= E_UP2 ; end end E_UP2 : begin if (( 1'b1 ) == 1'b1) begin SM <= Idle ; end end LoadF1 : begin if (( 1'b1 ) == 1'b1) begin SM <= A1toA0 ; end end A1toA0 : begin if (( 1'b1 ) == 1'b1) begin SM <= E_Up1 ; end end default : begin SM <= Idle; end endcase end 


该代码的顶部有声明(状态名称,Datapath链,编码自动机状态的寄存器)。 我们将它们转移到适当的位置
我们的代码部分:
 /* ==================== Wire and Register Declarations ==================== */ localparam [2:0] Idle = 3'b000; localparam [2:0] LoadF0 = 3'b001; localparam [2:0] LoadF1 = 3'b010; localparam [2:0] E_Up1 = 3'b100; localparam [2:0] A1toA0 = 3'b011; localparam [2:0] E_Down1 = 3'b101; localparam [2:0] SWAP = 3'b110; localparam [2:0] E_UP2 = 3'b111; wire hungry0; wire F0empty; wire hungry1; wire F1empty; wire Datapath_1_d0_load; wire Datapath_1_d1_load; wire Datapath_1_f0_load; wire Datapath_1_f1_load; wire Datapath_1_route_si; wire Datapath_1_route_ci; wire [2:0] Datapath_1_select; reg [2:0] SM; 


好吧,

信号结合位点是可转移的:
 /* ==================== Assignment of Combinatorial Variables ==================== */ assign Datapath_1_d0_load = (1'b0); assign Datapath_1_d1_load = (1'b0); assign Datapath_1_f0_load = (1'b0); assign Datapath_1_f1_load = (1'b0); assign Datapath_1_route_si = (1'b0); assign Datapath_1_route_ci = (1'b0); assign Datapath_1_select[0] = (SM[0]); assign Datapath_1_select[1] = (SM[1]); assign Datapath_1_select[2] = (SM[2]); 


现在该插入Datapath了。 从UDB编辑器移植的代码对于机器编辑很有用,但对于手动编辑却不是很好。 在那里,创建了链,这些链的一端连接到Datapath输入,另一端连接到常量。 但是在由数据路径配置工具创建的代码中(该代码工具完成了所有手动工作),所有输入已经直接连接到零常量。 因此,我将仅连接那些不是常量的行,但将从已传输的文本中删去所有与常量转发有关的内容。 连接结果如下所示(颜色突出显示了我在“数据路径配置工具”中自动创建的有关编辑的位置):



相同的文字:
 )) LCD_DP( /* input */ .reset(1'b0), /* input */ .clk(clk), /* input [02:00] */ .cs_addr(SM), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0), /* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(hungry0), /* output */ .f0_blk_stat(F0empty), /* output */ .f1_bus_stat(hungry1), /* output */ .f1_blk_stat(F1empty), 


并行数据要复杂一些。 数据路径具有一个八位端口,只需要将其中四个带出。 因此,我们启动辅助电路并将其仅连接到输出的一半:

 wire [7:0] tempBus; assign LCD_D = tempBus[7:4]; 

并像这样连接它:



相同的文字:
  /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po( tempBus) // Parallel data port ); 


我们尝试组装(Shift + F6或通过菜单项Build-> Generate Application )。 我们得到错误:



我们有端口hungry0hungry1 (在创建组件时显示),以及同名的链(在从示例中拖动时显示)。 只需卸下这些链条(离开端口)。 在某处时钟信号泄漏的地方,我们有了称为clk的电路。

删除所有不必要的电路(最初将零常量添加到Datapath输入的那些电路以及hungry0hungry1 )之后,我们将在文件开头获得以下代码:

 // Your code goes here /* ==================== Wire and Register Declarations ==================== */ localparam [2:0] Idle = 3'b000; localparam [2:0] LoadF0 = 3'b001; localparam [2:0] LoadF1 = 3'b010; localparam [2:0] E_Up1 = 3'b100; localparam [2:0] A1toA0 = 3'b011; localparam [2:0] E_Down1 = 3'b101; localparam [2:0] SWAP = 3'b110; localparam [2:0] E_UP2 = 3'b111; wire F0empty; wire F1empty; reg [2:0] SM; /* ==================== Assignment of Combinatorial Variables ==================== */ wire [7:0] tempBus; assign LCD_D = tempBus[7:4]; 

而且,当在机器的主体中用clk替换时钟时,我将同时抛弃所有有利于自动生成的行,但是使用手动编辑只会造成混乱(所有比较均得出无条件结果TRUE ,依此类推)。 特别是,在下面的示例中,您可以删除大约一半的行(有些开始/结束是可选的,有时会需要它们,因为我们将添加操作,因此我将其突出显示):



根据上述原理进行梳理后(并用clk替换clock ),这样的物体仍然保留

(它变得更短,这意味着它更易于阅读):
 always @ (posedge clk) begin : Idle_state_logic case(SM) Idle : begin if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; end else if (( !F1empty ) == 1'b1) begin SM <= LoadF1 ; end end LoadF0 : begin SM <= E_Up1 ; end E_Up1 : begin SM <= E_Down1 ; end E_Down1 : begin SM <= SWAP ; end SWAP : begin SM <= E_UP2 ; end E_UP2 : begin SM <= Idle ; end LoadF1 : begin SM <= A1toA0 ; end A1toA0 : begin SM <= E_Up1 ; end default : begin SM <= Idle; end endcase end 


现在,在编译过程中,我们被告知LCD_ELCD_RS电路未连接。

实际上,这是对的:



现在是时候向状态机添加动作了。 我们将用reg替换与未连接链相对应的端口的声明,因为我们将它们写在机器主体中(这是Verilog语言的语法,如果我们写的话,数据应该单击,为此我们需要一个触发器,并且由关键字reg给出):


相同的文字:
 module LCD4bit ( output hungry0, output hungry1, output [3:0] LCD_D, output reg LCD_E, output reg LCD_RS, input clk ); 


并用动作填充机器。 在考虑自动机的过渡图时,我已经说过上面的逻辑,所以我只显示结果:


相同的文字:
 always @ (posedge clk) begin : Idle_state_logic case(SM) Idle : begin LCD_E <= 0; if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; LCD_RS <= 0; end else if (( !F1empty ) == 1'b1) begin SM <= LoadF1 ; LCD_RS <= 1; end end LoadF0 : begin SM <= E_Up1 ; end E_Up1 : begin SM <= E_Down1 ; LCD_E <= 1'b1; end E_Down1 : begin SM <= SWAP ; LCD_E <= 1'b0; end SWAP : begin SM <= E_UP2 ; end E_UP2 : begin SM <= Idle ; LCD_E <= 1; end LoadF1 : begin SM <= A1toA0 ; end A1toA0 : begin SM <= E_Up1 ; end default : begin SM <= Idle; end endcase end 


从这一刻起,项目开始组装。 但是他还不会工作。 到目前为止,我曾著名地说过:“在这种状态下,我们将从FIFO加载寄存器”,“ A1将在此复制到A0”,“半字节将在此交换”。 总的来说,我说了很多,但到目前为止,还没有采取任何行动。 实现它们的时候到了。 我们看一下状态的编码方式:

 localparam [2:0] Idle = 3'b000; localparam [2:0] LoadF0 = 3'b001; localparam [2:0] LoadF1 = 3'b010; localparam [2:0] E_Up1 = 3'b100; localparam [2:0] A1toA0 = 3'b011; localparam [2:0] E_Down1 = 3'b101; localparam [2:0] SWAP = 3'b110; localparam [2:0] E_UP2 = 3'b111; 

重新打开数据路径配置工具



并开始编辑CFGRAM行。 编辑时,应牢记数据路径方案,即:



下图中的红色框(和上图中的箭头)突出显示了状态LoadF0 (代码001,即Reg1 )的校正区域(和数据路径)。 我也手动输入了评论。 F0的内容应进入A0。



我用绿色框和箭头标记了状态LoadF1(代码010- Reg2 )的设置和路径。

我用蓝色框和箭头标记了状态A1toA0(代码011- Reg3 )的设置和路径。

紫色的框和箭头标记了SWAP状态的设置和路径(代码110- Reg6 )。

最后,橙色箭头显示并行数据路径。 他们没有采取任何行动。 它们总是来自SRCA 。 我们几乎总是选择A0作为SRCA :数据来自A0。 因此,要重定向输入数据,我们将不得不执行许多辅助操作,但是我们不接受任何数据,因此在这里我们不需要这些操作,每个人都可以在AN82156中找到他们的列表。 我们也不需要编辑任何静态Datapath设置,因此请关闭Datapath Config Tool

仅此而已。 构思的硬件已完成。 开始开发C代码。 为此,请转到“ 源”选项卡并编辑main.c文件。



常规的LCD初始化和“ ABC”字符输出看起来像这样(我提醒您,命令进入FIFO0 ,文档需要在团队之间插入暂停,数据进入FIFO1 ,我没有发现有关数据之间的暂停的任何信息):

  volatile uint8_t* pFIFO0 = (uint8_t*) LCD4bit_1_LCD_DP__F0_REG; volatile uint8_t* pFIFO1 = (uint8_t*) LCD4bit_1_LCD_DP__F1_REG; pFIFO0[0] = 0x33; CyDelay (5); pFIFO0[0] = 0x33; CyDelay (100); pFIFO0[0] = 0x33; CyDelay (5); pFIFO0[0] = 0x20; CyDelay (5); pFIFO0[0] = 0x0C; //   CyDelay (50); pFIFO0[0] = 0x01; //   CyDelay (50); pFIFO1[0] = 'A'; pFIFO1[0] = 'B'; pFIFO1[0] = 'C'; 

什么是 为什么屏幕上只有第一个字符?



如果您在数据输出之间添加延迟,则一切都很好:



示波器没有足够的通道来进行此类工作。 我们检查逻辑分析器上的工作。 记录数据的过程如下。



所有数据都就位(三对包)。 安装和捕捉数据的时间分配了足够的数量。 通常,从时间图的角度来看-一切都正确完成。 解决了科学问题,形成了所需的时间图。 这是工程-不。 原因是LCD上安装的处理器较慢。 在字节之间,添加延迟。

我们将使用7位计数器来形成延迟,同时我们将训练将其添加到这样的系统中。 让我们处于空闲状态不少于某个给定的时间,并且一个七位计数器将为我们测量该时间。 同样,我们不会编写代码,而是创建代码。 因此,我们再次转到UDB编辑器的辅助组件,并向工作表添加一个计数器,如下所示设置其参数:



该计数器将始终工作( 启用设置为1)。 但是它将在计算机处于E_UP2状态(此后我们立即进入空闲状态)时加载。 当计数器计数到零时, Count7_1_tc行将增加到1,这将为退出Idle状态提供附加条件。 该图还包含该期间的值,但是我们不会在Verilog代码中找到它。 必须将其输入C代码中。 但是首先,我们通过切换到“ Verilog”选项卡来传输自动生成的Verilog代码。 首先,应该连接计数器(我们在文件开头看到此代码,并将其移到开头):

 `define CY_BLK_DIR "$CYPRESS_DIR\..\psoc\content\CyComponentLibrary\CyComponentLibrary.cylib\Count7_v1_0" `include "$CYPRESS_DIR\..\psoc\content\CyComponentLibrary\CyComponentLibrary.cylib\Count7_v1_0\Count7_v1_0.v" 

已经描述了如何对行和常量进行创造性的细化,因此我将仅显示结果。 结果是添加的链和赋值(其余的设置了常量,所以我将其丢弃):

 wire Count7_1_load; wire Count7_1_tc; assign Count7_1_load = (SM==E_UP2); 

这是计数器本身,位于文件末尾。 在此声明中,所有常量都直接分配给端口:

  Count7_v1_0 Count7_1 ( .en(1'b1), .load(Count7_1_load), .clock(clk), .reset(1'b0), .cnt(), .tc(Count7_1_tc)); defparam Count7_1.EnableSignal = 1; defparam Count7_1.LoadSignal = 1; 

为使此计数器正常工作,我们自动添加了附加条件以退出“ 空闲”状态:


相同的文字:
  case(SM) Idle : begin LCD_E <= 0; if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; LCD_RS <= 0; end else if (( !F1empty &Count7_1_tc ) == 1'b1) begin SM <= LoadF1 ; LCD_RS <= 1; end end 


不会创建以这种方式添加的计数器的API,因此我们在函数中添加了两条魔术线,它们是我在图像中形成的,并且类似于过去项目在API中看到的样子(第一行设置帐户的加载值,相同的Load,第二行设置计数器):

  *((uint8_t*)LCD4bit_1_Count7_1_Counter7__PERIOD_REG) = 0x20; *((uint8_t*)LCD4bit_1_Count7_1_Counter7__CONTROL_AUX_CTL_REG) |= 0x20; // Start 

分析器显示,在修改的情况下,延迟很明显:



LCD还具有所有三个字符。

但是现实生活中的程序化字符输出是不可接受的。 仅将它们添加到FIFO中将溢出。 等待FIFO清空-这意味着为处理器内核造成较大的延迟。处理器以72 MHz的频率工作,并以1 MHz的频率输出7-8个时钟周期的数据。因此,在现实生活中,必须使用DMA显示文本。这就是“启动并忘记”原则派上用场的地方。时序图的所有延迟都将由UDB生成,而DMA控制器将确定FIFO的就绪状态,以接收给我们的数据。处理器内核只需要在内存中形成一行并配置DMA,之后它就可以执行其他任务,而不必担心LCD的输出。

添加以下代码:
  static const char line[] = "This is a line"; /* Defines for DMA_D */ #define DMA_D_BYTES_PER_BURST 1 #define DMA_D_REQUEST_PER_BURST 1 /* Variable declarations for DMA_D */ /* Move these variable declarations to the top of the function */ uint8 DMA_D_Chan; uint8 DMA_D_TD[1]; /* DMA Configuration for DMA_D */ DMA_D_Chan = DMA_D_DmaInitialize(DMA_D_BYTES_PER_BURST, DMA_D_REQUEST_PER_BURST, HI16(line), HI16(LCD4bit_1_LCD_DP__F1_REG)); DMA_D_TD[0] = CyDmaTdAllocate(); CyDmaTdSetConfiguration(DMA_D_TD[0], sizeof(line)-1, CY_DMA_DISABLE_TD, CY_DMA_TD_INC_SRC_ADR); CyDmaTdSetAddress(DMA_D_TD[0], LO16((uint32)line), LO16((uint32)LCD4bit_1_LCD_DP__F1_REG)); CyDmaChSetInitialTd(DMA_D_Chan, DMA_D_TD[0]); CyDmaChEnable(DMA_D_Chan, 1); 


在屏幕上,我们有:



结论


在半合成但接近实际任务的示例中,我们已经掌握了使用另一种机制-数据路径配置工具为UDB开发代码的机制。与UDB编辑器不同,此机制可提供对所有UDB管理功能的访问权限,但使用该机制比使用UDB Editor更为复杂。但是,本文作者提出的方法不允许从头开始编写代码,而只能依靠同一UDB编辑器创建的辅助代码来创建代码。

撰写本文时获得的测试项目可以在此处进行

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


All Articles