⬆️ ⬇️

Using the Datapath Config Tool





We have to take the penultimate step in the practical development of working with UDB. Today we will be developing not using the automated UDB Editor, but in the semi-manual mode, using the Datapath Config Tool. A very good tool for mastering this tool is AN82156 - PSoC 3, PSoC 4, and PSoC 5LP - Designing PSoC Creator Components with UDB Datapaths. Actually, I myself learned from it.



Perhaps, while reading our translations of the documentation on UDB , someone tried to reproduce the knowledge from there in practice and noticed that not all the functionality described in the publications is available in the UDB Editor. This is due to the fact that the developers did not place some particularly cumbersome mechanisms in the UDB Editor. The authors of AN82156 claim that it’s impossible to do the following things through the UDB Editor:





From myself, I add that I did not find how to implement a permutation of nibbles in the UDB Editor.

')

If these functions are needed in a project, you will need to create your own code in the Verilog language. I specifically used the word "create", not "write." It is enough to know this programming language at the reading level. I mean, you need to understand what design is needed for what. And to be able to write from scratch is always useful, but for this article, this skill is not mandatory.



As a solvable problem, I chose the semi-synthetic case. In general, I decided to bring some data to the parallel port, and in particular, from what is at hand, the parallel port is in the text LCD display. I pulled it out three years ago from the MZ3D 3D printer when I transferred it to the STM32. Therefore, the case is semi-synthetic: today, such indicators usually have I2C input, and they do not need to connect through a pile of wires in real life. However, parallel ports for modern LCDs are also available, so everyone can use them to repeat the experiment.



Consider the display enablement scheme taken from the reprap.org site (it was not easy, my provider blocks this site, as well as a number of other technical ones, arguing that he lives on the same IP with someone blocked).







Great scheme! First, I do not need to think about reading: the data in the LCD can only be written (the R / W line is grounded and not available on the connector). Secondly, the data goes in a 4-bit format, which means that we can not only work out parallel output, but also check the operation of the nibbling permutation function.



Project creation



So, run PSoC Creator and select File-> New-> Project :







Next, I choose my breadboard:







Next is an empty diagram:







I will name the project LCDTest2 :







Now, as before, go to the Components tab:







And, selecting the project, press the right mouse button, then select Add Component Item .







And here it is necessary to choose the Symbol Wizard . Give the name ... Well, let's say, LCD4bit .







I have assigned the following ports to the symbol:







clk is the clock input. Ports with an LCD prefix are standard LCD ports. hungry - outputs that tell the DMA block that there is free space in the FIFO, the idea was considered in an article about controlling RGB LEDs . Click OK, we get the symbol.







Now, based on this symbol, you should generate a Verilog template. Click the right mouse button in the vicinity of the symbol and select Generate Verilog in the context menu.







We have a template shown in the figure below (in the text form, it still does not make any sense):







We have created a module and some sections. But so far they have not created a Datapath. To add it, go to the project tree, select the LCD4bit.v file, click the right mouse button and in the appeared context menu select the Datapath Config Tool :







A window opens in front of us, which I will show only partially:







Please love and favor, editor Datapath. It contains all the bits that were described in the translation of the company documentation. But there are so many of these bits that in the early days I looked at him, but I was afraid to do anything. I will look and see and come out. And only after some time, having become accustomed, I began to try to do something. Actually, that's why I brought only part of the window. Why scare everyone ahead of time? In the meantime, we just need to create a Datapath, so we select the menu item Edit-> New Datapath :







Which option to choose in the dialog that appears?







The question is a bit more serious than it seems. Let me even highlight the next paragraph, so that no one comes across (I got caught myself, and then I saw questions from the network that came across, and no one answered them properly, but the answer is in AN82156 , you just need to read not diagonally, since there it is written short discreet phrase).

If you plan to work with parallel data, then you must choose the option CY_PSOC3_DP. No other option will contain ports for connecting parallel data.
So. Let the instance we have name LCD_DP:







Click OK and for now close the Datapath Config Tool , agreeing to save the result. We will come back here later.



Our Verilog code has expanded. Now it has a datapath. The beginning is completely unreadable. It's not scary, it is configured by the Datapath Config Tool .







And we will rule the end of the description of Datapath. Our site looks like this
(from this place it makes sense to bring everything in text form).
)) 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 ); 




Fearfully? Now we will understand what is what - will cease to be scary. In fact, there are three distinct groups in this text. Let's recall the translation of the documentation. What did the datapath look like in the picture? I will immediately mark in the figure the places to which the groups "1", "2" and "3" belong.







Actually, the first group of ports in the verilog code is the inputs. Compare the names at the output of the multiplexer of the inputs (“1” in the figure) and the names of the signals in the code.



Now all inputs are zeroed. We will need to connect a clock input and we will be able to forward up to six input lines, as we did in the UDB Editor. These inputs are:



  /* 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), 


The second group is exits. The names in the code also coincide with the names of the inputs of the output multiplexer "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(), 


The third group is only for this type of Datapath (for the rest it is absent, therefore there are no parallel data). These are internal Datapath signals, through which you can independently produce chaining or other useful actions. The names in the code also coincide with the names of the internal signals scattered throughout the figure. We through one of them (the last one in the list, his name is po ) will output parallel data directly to the legs of the chip.



  /* 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 ); 


So. As we work, we will have to connect part of these inputs and outputs to our own entities, and the rest will just be left in the form in which we created them.



Using UDB Editor as a reference



And here we got a blank, we know where and what we have to write. It remains to understand what exactly we will enter there. It so happened that I do not use Verilog every day, so I remember everything in general terms, and writing from scratch for me is always a stressful situation. When the project is already underway - it all comes to mind, but if after a couple of months of inactivity start something from scratch, of course, I do not remember the details of the syntax of this particular language. Therefore, I propose to ask the development environment to help us.



UDB Editor for self-control builds Verilog code. We take advantage of the fact that components that are not involved in the main circuit are not compiled, so we can easily create an auxiliary component in the UDB Editor, and it will not fall into the output code. We will draw an automaton there, we will make a rough setup of the inputs and outputs of Datapath, and then simply transfer the automatically created text to our verilog module and creatively refine everything. It's much easier than remembering the verilog syntax details and writing everything from scratch (although, who constantly use Verilog, it will of course be easier to write from scratch: creative refinement, as we will soon see, is simple, but requires time).



So, we start to make an auxiliary component. With a familiar hand movement we add a new element to the project:







This will be a UDB document, let's call it UDBhelper :







It's time to think about the machine, which we place on the created sheet. To do this, we need to consider what kind of time diagram we need to form with it:











So. You must first set the RS signal (since R / W is soldered to zero by hardware). Next, you should wait for tAS, then raise the signal E and set the data (setting data on the positive edge E is not limited). The data must be on the bus no less than tDSW, after which the signal E should be dropped. The data must remain on the bus for at least tDHW and the RS at least tAH.



RS is a “command or data” flag. If RS is zero, then the command is written, if the unit is data.



I suggest sending commands via FIFO0 , and sending data through FIFO1 . In the context of the current task, this does not contradict anything. Then the state machine proposed by me will have the following form:







In the Idle state, the machine is in as long as none of the FIFOs have data. If there is data in FIFO0 , it goes to the state LoadF0 , where in the future it will receive data from FIFO0 to A0.



While the commands are being transmitted, the data should not be sent. Therefore, the condition for receiving data will be lower in priority than the condition for receiving commands.







Data is received in A1 in the LoadF1 state (from FIFO1, it can only get into register A1 and cannot get into register A0), and then copied from A1 to A0 in state A1toA0 .



Whatever way we go to the location of the arrows, we have data in A0. They are already output to the parallel port. We cock E (in the E_UP1 state), we drop E (in the E_DOWN1 state). Next, we will have a state for exchanging nibbles ( SWAP ), after which E rises again ( E_UP2 ). With this I have exhausted eight states that can be encoded with three bits. And we remember that the Datapath dynamic configuration RAM has only three address inputs. One could apply some tricks, but the article and so it turns out great. Therefore, just the second time E will be dropped in the Idle state. Then eight states will be enough for us.



On the sheet we also put the Datapath and assign its inputs and outputs in the way already familiar from the previous articles. Here are the inputs:







Here are the outputs:







Nothing new, everything has already been described in previous articles of the cycle. So, we have a blank, on the basis of which we can make something of our own. However, to make sure that everything is going, we need to bring our system to the top level of the project, otherwise there will be no errors. And with the primary experiences without errors will not work. Therefore, we will do another auxiliary action.



The description of how the scheme is made goes beyond the description of working with UDB. I'll just show you what scheme I got. There is only one DMA block: when sending commands to the LCD, it is necessary to withstand long pauses, so it is still easier to do this programmatically. For other applications, you can simply put the second DMA block by analogy, using the signal hungry0 .







In order to accurately meet the time frame, I chose a clock frequency equal to one megahertz. It would be possible to take the frequency and higher, but the data is transmitted over long wires in conditions of high interference, so the time to install the data before and after the gate is better to take with a margin. If someone repeats my experiments on the same breadboard - do not use port P3.2: a capacitor is soldered on the board to this leg. I killed half an hour, until I found out why the impulse E, which I first connected there, is not forming. I threw it on P3.1 - everything worked right away. My data bus goes to P3.7-P3.4, RS goes to P3.3, so E initially went to P3.2 ...



Here you go. Now, if you try to compile the project, we get quite predictable errors







So the system is trying to collect something. But she has nothing to collect yet. We start copying the code. To do this, in the UDB Editor, switch to the Verilog tab (this tab is located below the window with the UDB Editor sheet):







What is there familiar? At the very end of the text is the body of the machine. Let's start the transfer from it.



Also place it under the datapath:
 /* ==================== 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 




On top of this code are declarations (names for states, chains for Datapath, a register encoding the state of the machine). We transfer them to the appropriate
section of our code:
 /* ==================== 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; 




Well



signal binding site is transferable:
 /* ==================== 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]); 




It's time to connect the Datapath. The code transferred from the UDB Editor is good for machine editing, but not very good for manual editing. Circuits are created there that are connected at one end to the inputs of the Datapath, and the other to the constants. But in the code created by the Datapath Configuration Tool (which does everything for manual work), all inputs are already connected to zero constants directly. So I will connect only those lines that are not constants, but I will cut everything that relates to forwarding of constants from the transferred text. The connection turned out this way (the places that I edited relative to the automatically created in the Datapath Configuration Tool are highlighted in color):







Same text:
 )) 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), 




Parallel data is a bit more complicated. Datapath has an eight-bit port, and only four of them need to be brought out. Therefore, we start the auxiliary circuit and connect only its half to the output:



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


And we connect everything like this:







Same text:
  /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po( tempBus) // Parallel data port ); 




We try to build (Shift + F6 or through the menu item Build-> Generate Application ). We get the error:







We have ports hungry0 and hungry1 (appeared when creating a component), as well as chains of the same name (appeared when dragging from a sample). Simply remove these circuits (leaving the ports). And somewhere the clock signal leaked, and here this chain is called clk .



After removing all the extra circuits (those that initially sent zero constants to the inputs of Datapath, as well as hungry0 and hungry1 ), we get the code for the beginning of our file:



 // 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]; 


And when replacing clock with clk in the body of the machine, I will also throw out all the lines that are good for autogeneration, but for manual editing, they only create confusion (all comparisons that give an TRUE absolute result, etc.). In particular, in the example below, about half of the lines can be crossed out (and some begin / end are optional, sometimes they will be needed, because we will add actions, I highlighted them in color):







After combing by the above principle (and replacing clock with clk ), such a body remains



(it has become shorter, which means it is easier to read):
 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 




Now we are told during compilation that the LCD_E and LCD_RS circuits are not connected.



Actually, this is true:







It's time to add a finite state machine action. Let's replace the declarations of the corresponding non-connected port chains with reg , since we will write them in the body of the machine (this is the syntax of the Verilog language, if we write, the data must click, it needs a trigger, and it is given by the keyword reg ):





Same text:
 module LCD4bit ( output hungry0, output hungry1, output [3:0] LCD_D, output reg LCD_E, output reg LCD_RS, input clk ); 




And fill the machine actions. I have already spoken logic above when I looked at the transition graph of the automaton, so I will show only the result:





Same text:
 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 




From this moment on, the project begins to be built. But he will not work yet. So far, I famously said: “In this state, we will load the register from the FIFO”, “This will copy A1 into A0”, “This will rearrange nibbles”. In general, I said a lot, but there was no action yet. It's time to execute them. We look, as at us states were coded:



 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; 


Reopen the Datapath Configuration Tool :







And we begin to edit the lines CFGRAM . When editing, keep the Datapath diagram in front of your eyes, namely:







The red frames in the figure below (and the arrows in the figure above) highlighted the corrected sections (and the data path) for the LoadF0 state (code 001, that is, Reg1 ). I also entered the comments manually. The contents of F0 should go to A0.







With green frames and arrows, I marked the settings and the path for the state of LoadF1 (code 010 - Reg2 ).



With blue frames and arrows, I marked the settings and the path for the state A1toA0 (code 011 - Reg3 ).



With purple frames and arrows, I marked the settings and the path for the SWAP state (code 110 - Reg6 ).



Finally, the orange arrows indicate the parallel data path. And no action is taken for them. They always come out of the SRCA . We almost always have A0 selected as the SRCA : the data comes out of A0. Here, to redirect input data, we would have to perform a lot of auxiliary actions, but we don’t accept any data, so here we don’t need these actions, and everyone will find their list in AN82156 . Also, we do not need to edit any static Datapath settings, so we close the Datapath Config Tool .



Everything. The conceived hardware is complete. Getting to the development of C code. To do this, go to the Source tab and edit the file main.c.







The standard initialization of the LCD and the output of the “ABC” characters look like this (remember, the commands go to FIFO0 , the documentation requires inserting pauses between commands, and the data goes to FIFO1 , I didn’t find anything between the data pauses):



  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'; 


What? Why is there only the first character on the screen?







And if you add delays between the output, everything is fine:







Oscilloscope for such work is not enough channels. We check the work on the logic analyzer. The process of recording data is as follows.







All data is in place (three pairs of parcels). Time to install and snap data is allocated in sufficient volume. In general, in terms of time diagrams - everything is done right. The scientific problem is solved, the desired time diagrams are formed. Here is engineering - no. Blame it all - the slowness of the processor installed in the LCD. Between bytes, you need to add delays.



We will form delays using a seven-bit counter, at the same time we will practice adding it to such a system. Let we will be in the Idle state not less than a certain specified time, and the seven-bit counter will measure us this time. Again, we will not write, but create code. Therefore, we again go to the auxiliary component of the UDB Editor and add a counter to the sheet, adjusting its parameters as follows:







This counter will always work ( Enable is set to 1). But it will be loaded when the machine is in the E_UP2 state (after which we immediately fall into the Idle state). The line Count7_1_tc will be set to 1 when the counter counts to zero, which we will make an additional condition for exiting the Idle state. The figure also contains the period value, but we will not find it in the Verilog code. It will have to be entered into the C code. But first we transfer the automatically generated Verilog code by switching to the Verilog tab. First of all, the counter should be connected (we see this code at the beginning of the file and move it to the beginning too):



 `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" 


How creative refinement of the lines and constants is performed has already been described, so I will simply show the result. Here are the chains and assignments added as a result (the rest asked for constants, so I threw them out):



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


But the counter itself, placed at the end of the file. All constants are assigned to ports right in this ad:



  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; 


To enable this counter to work, the machine adds an additional condition to exit the Idle state:





Same text:
  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 




The API for the counter added in this way is not created, so we add two magic lines to the main function, which I formed in the image and similarity of what I saw in the API from past projects (the first line sets the load value of the account, the Load, the second starts the counter)



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


The analyzer shows that in the modified case of delay there is:







The LCD also has all three characters.



But the programmatic output of characters in real life is unacceptable. If you simply add them to the FIFO, an overflow will occur.To wait for the FIFO to be emptied is to create large delays for the processor core. The processor operates at 72 MHz, and the data is output in 7-8 clock cycles at a frequency of 1 MHz. Therefore, in real life, the text should be displayed using DMA. This is where the “Launched and forgotten” principle comes in handy. All delays for the timing diagram will be generated by the UDB, and the DMA controller will determine the readiness of the FIFO to receive data. From the processor core is required only to form a line in memory and configure the DMA, after which it can be engaged in other tasks without worrying about output to the LCD.



Add this code:
  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); 




On the screen we have:







Conclusion



On a semi-synthetic, but close to real-world, example, we mastered the mechanism for developing code for UDB using an alternative mechanism — the Datapath Config Tool. This mechanism, unlike the UDB Editor, gives access to absolutely all the UDB control capabilities, but working with it is more complicated than with the UDB Editor. Nevertheless, the method proposed by the author of the article makes it possible not to write code from scratch, but simply to create it, based on auxiliary code created by the same UDB Editor.



The resulting test project when writing an article can be found here .

Source: https://habr.com/ru/post/442572/



All Articles