
在与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 )。 我们得到错误:

我们有端口
hungry0和
hungry1 (在创建组件时显示),以及同名的链(在从示例中拖动时显示)。 只需卸下这些链条(离开端口)。 在某处
时钟信号泄漏的地方,我们有了称为
clk的电路。
删除所有不必要的电路(最初将零常量添加到Datapath输入的那些电路以及
hungry0和
hungry1 )之后,我们将在文件开头获得以下代码:
// 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_E和
LCD_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编辑器创建的辅助代码来创建代码。撰写本文时获得的测试项目可以在此处进行。