{"id":246,"date":"2013-10-08T04:40:01","date_gmt":"2013-10-08T04:40:01","guid":{"rendered":"http:\/\/burnt-traces.com\/?p=246"},"modified":"2014-03-27T04:32:14","modified_gmt":"2014-03-27T04:32:14","slug":"lcd-module-interface-part-1","status":"publish","type":"post","link":"https:\/\/burnt-traces.com\/?p=246","title":{"rendered":"LCD Module Interface, Part 1"},"content":{"rendered":"<p>A long time ago I picked up an old LCD module (<a href=\"http:\/\/www.allelectronics.com\/make-a-store\/item\/LCD-104\/640-X-480-LCD-W\/CCFL-BACKLIGHT-AND-INVERTER\/1.html\">this one<\/a>, if its still there). Now the trouble with this module is that it does not have a built in controller. It&#8217;s more like a TV or monitor in that it needs to constantly be fed a signal containing the image data to display. Now a standard micro controller might have trouble doing this on its own, it would either require a bit of external circuitry, or have to dedicate the majority of it&#8217;s processing time to updating the display. This is a surplus part, and the datasheet provided is poorly xeroxed and lacking specific implementation details. However it is a nice monochrome display at 640&#215;480 and well worth the price of $5.50, if you can get it to work. Luckily for me, I found one other person on the whole internet who managed to get this same display working, and remnants of his work for the MIT Model Railroad club were found here,<a href=\"http:\/\/www.nyx.net\/~jpurbric\/lcd\/\"> http:\/\/www.nyx.net\/~jpurbric\/lcd\/<\/a>. Now he managed to implement this using a PIC micro, some discrete logic and some external memories. My plan is to first attempt to drive this from my Papilio board. I had a good start since this other project made really nice <a href=\"http:\/\/www.nyx.net\/~jpurbric\/lcd\/timing.gif\">timing diagram<\/a> to follow for the display. <\/p>\n<p>First in implementing the VHDL, I defined as many constant values as I could think up. Using the timing diagram and the datasheet, I setup some time period and count constants. <\/p>\n<pre class=\"brush: vhdl; title: ; notranslate\" title=\"\">\r\n--Timing Constants \r\n--Time constant integers in nanoseconds\r\nconstant CLK_PERIOD : integer := 31; -- approximate\/rounded off\r\nconstant XSCL_PERIOD : integer := 166 ;\r\n\r\n--FRAME CONSTANTS \t\r\nconstant WIDTH : integer := 640;\r\nconstant HEIGHT : integer := 480;\r\nconstant XPULSES : integer := WIDTH \/ 4;\r\nconstant YPULSES : integer := 242;\r\n\r\n<\/pre>\n<p>The timings I defined at the minimums defined in the datasheet. The nice part about utilizing constants like this, is that I can adjust these later and the whole design should adjust. The CLK_PERIOD constant is the main clock period, so it will be used to keep track of elapsed time. The XSCL_PERIOD is the minimum period of the XSCL pulse or &#8220;X Shift Clock&#8221;. Looking at the timing diagram it seems that the data is read in on the negative edge of this signal. At first I thought of several other timing constants, but as I worked out the design, they didn&#8217;t prove to be necessary. The other constants are pretty obvious, the HEIGHT and WIDTH of the screen in pixels, and the number of YPULSES and XPULSES to be sent in each frame of the signal. If you notice these numbers don&#8217;t match up to the size of the display. This display is organized as two screens, an upper and lower half. It&#8217;s also 1 bit per pixel. So, of the 8 bit data bus, 4 bits go to the top and 4 go to the bottom. So it only needs to write 160 times for each line to get 640 pixels and it will write 242 lines. Yeah, there are 2 extra lines in the signal but the don&#8217;t make it to the display. And now for a couple signals to get started with. <\/p>\n<pre class=\"brush: vhdl; title: ; notranslate\" title=\"\">\r\n --Timing signals\r\nsignal XSCL_TIME : integer range 0 to (XSCL_PERIOD + CLK_PERIOD) := 0;\r\n\r\n--count signals \r\nsignal XSCL_CNT : integer range 0 to 160 := 0;\r\nsignal YSCL_CNT : integer range 0 to 242 := 0;\r\n<\/pre>\n<p>The first is my main time delay count. XSCL_TIME will just add on the clock period each time through the main process to make sure we don&#8217;t violate our minimum time for the signal. Its defined as a range from zero to XSCL_PERIOD + CLK_PERIOD because I wanted to be sure that whatever size value it ends up being has enough room exceed the XSCL_PERIOD by one clock cycles worth. The XSCL_CNT and YSCL_CNT are regular increment counters to keep track of where we are in the signal and be the main drivers of the state machine.<\/p>\n<pre class=\"brush: vhdl; title: ; notranslate\" title=\"\">\r\nsignal STARTED : STD_LOGIC := '0';\r\n<\/pre>\n<p>The STARTED signal is just a flag that&#8217;s used to have the signal go through as start up process. After the start up, STARTED is set to 1 and stays there. <\/p>\n<pre class=\"brush: vhdl; title: ; notranslate\" title=\"\">\r\n--Output Signals \r\nsignal LP_YSCL : STD_LOGIC := '0';\r\nsignal XSCL : STD_LOGIC := '0';\r\nsignal UD : STD_LOGIC_VECTOR(3 downto 0) := &quot;0000&quot;;\r\nsignal LD : STD_LOGIC_VECTOR(3 downto 0) := &quot;0000&quot;;\r\nsignal DIN : STD_LOGIC := '0';\r\n<\/pre>\n<p>These are the various output signals, three timing signals and the data signals. For now we will just be sending test pattern on the data. Now for the main process. I think its well commented so I won&#8217;t give a line by line explaination. It&#8217;s basically a simple state machine, using the current state of the output signals, the XSCL_CNT\/YSCL_CNT, and XSCL_TIME signals to track where it is in the process. It follows right along with the timing diagram.  Remember that in the process, signals are not updated until after the process. <\/p>\n<pre class=\"brush: vhdl; title: ; notranslate\" title=\"\">\r\n\t--Process Definition\r\n\tmain: process(clk)\r\n   begin\r\n\t\t-- triggers action on rising edge of clock signal\r\n     if rising_edge(clk) then\r\n\t    --increment counter, using time type\r\n\t\t XSCL_TIME &lt;= XSCL_TIME + CLK_PERIOD;\r\n\t\t \r\n\t\t \r\n\t\t if STARTED='0' then\r\n\t\t   --initial state\r\n\t\t\t--first part of timing diagram, on LP pulse, and a up\/down on XSCL\r\n\t\t\tif LP_YSCL = '0' then \r\n\t\t\t\t--set signals\r\n\t\t\t\tLP_YSCL &lt;= '1';\r\n\t\t\t\tXSCL &lt;= '1';\r\n\t\t\t\t--reset times\r\n\t\t\t\tXSCL_TIME &lt;=0;\r\n\t\t\telsif LP_YSCL = '1' and \r\n\t\t\t\t\t\tXSCL = '1' AND\r\n\t\t\t\t\t\tXSCL_TIME &gt; (XSCL_PERIOD\/2) then \r\n\t\t\t\t--set signal\r\n\t\t\t\tXSCL &lt;= '0';\r\n\t\t\telsif LP_YSCL = '1' AND\r\n\t\t\t\t\t\tXSCL = '0' AND \r\n\t\t\t\t\t\tXSCL_TIME &gt; XSCL_PERIOD THEN \r\n\t\t\t\t--clear started flag\r\n\t\t\t\tSTARTED &lt;= '1';\r\n\t\t\t\t--reset timer\r\n\t\t\t\tXSCL_TIME &lt;= 0;\r\n\t\t\tend if;\r\n\t\telse\r\n\t\t\t--after started\r\n\t\t\tif LP_YSCL = '0' and\r\n\t\t\t\tXSCL = '0' and \r\n\t\t\t\tXSCL_CNT = 0 THEN\r\n\t\t\t\t--occurs at the end of a row, timing specs say LP_YSCL must go to zero before XSCL goes high\r\n\t\t\t\t--Raise XSCL to HI\t\t\t\r\n\t\t\t\tXSCL &lt;= '1';\r\n\t\t\t\t--reset pulse timer \r\n\t\t\t\tXSCL_TIME &lt;= 0;\r\n\t\t\t\t\r\n\t\t\t\t--update X count \r\n\t\t\t\tXSCL_CNT &lt;= XSCL_CNT + 1;\r\n\t\t\telsif LP_YSCL = '1' and \r\n\t\t\t--first step, LP is H, XSCL is L, XSCL_CNT is 0\r\n\t\t\t\tXSCL = '0' AND\r\n\t\t\t\tXSCL_CNT = 0 THEN \r\n\t\t\t\t\r\n\t\t\t\t--drop LP, Raise XSCL, load data\r\n\t\t\t\tLP_YSCL &lt;= '0';\r\n\t\t\t\tXSCL &lt;= '1';\r\n\t\t\t\t\r\n\t\t\t\t--temp data \r\n\t\t\t\tUD &lt;= &quot;0101&quot;;\r\n\t\t\t\tLD &lt;= &quot;0101&quot;;\r\n\t\t\t\t\r\n\t\t\t\t--reset timer\r\n\t\t\t\tXSCL_TIME &lt;= 0;\r\n\t\t\t\t\r\n\t\t\t\t--if first row, raise frame pulse\r\n\t\t\t\tif YSCL_CNT = 0 then \r\n\t\t\t\t\tDIN &lt;= '0';\r\n\t\t\t\tend if;\r\n\t\t\telsif XSCL = '1' AND \r\n\t\t\t\t\tXSCL_TIME &gt; (XSCL_PERIOD\/2) THEN\r\n\t\t\t\t--drop xscl to L\r\n\t\t\t\tXSCL &lt;= '0';\r\n\t\t\t\t\r\n\t\t\telsif XSCL = '0' AND \r\n\t\t\t\t\tXSCL_TIME &gt; XSCL_PERIOD THEN\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t--load new data\r\n\t\t\t\t--TODO\r\n\t\t\t\t\r\n\t\t\t\t--IF at last pulse raise LP to hi\r\n\t\t\t\tif XSCL_CNT = XPULSES - 2 then -- use pulses-2 to compensate for cnt not updated yet and zero based index.\r\n\t\t\t\t\t--last X pulse, raise LP\r\n\t\t\t\t\tLP_YSCL &lt;= '1'; \r\n\t\t\t\t\t--update X count \r\n\t\t\t\t\tXSCL_CNT &lt;= XSCL_CNT + 1;\r\n\t\t\t\t\t\r\n\t\t\t\t\t--Raise XSCL to HI\t\t\t\r\n\t\t\t\t\tXSCL &lt;= '1';\r\n\t\t\t\t\t--reset pulse timer \r\n\t\t\t\t\tXSCL_TIME &lt;= 0;\r\n\t\t\t\t\t\r\n\t\t\t\telsif XSCL_CNT = XPULSES - 1 THEN \r\n\t\t\t\t   --new line, drop LP\r\n\t\t\t\t\tLP_YSCL &lt;= '0';\r\n\t\t\t\t\t--reset XSCL_CNT\r\n\t\t\t\t\tXSCL_CNT &lt;= 0;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif YSCL_CNT = YPULSES - 1 then \r\n\t\t\t\t\t\t-- this was the last pulse, go back to zero\r\n\t\t\t\t\t\tYSCL_CNT &lt;= 0;\r\n\t\t\t\t\telse\t\t\t\t\t\t\r\n\t\t\t\t\t\t--increment Y count\r\n\t\t\t\t\t\tYSCL_CNT &lt;= YSCL_CNT + 1;\r\n\t\t\t\t\tend if;\r\n\t\t\t\t\t\r\n\t\t\t\telse\t\t\t\t\t\r\n\t\t\t\t\t--Raise XSCL to HI\t\t\t\r\n\t\t\t\t\tXSCL &lt;= '1';\r\n\t\t\t\t\t--reset pulse timer \r\n\t\t\t\t\tXSCL_TIME &lt;= 0;\r\n\t\t\t\t\t--update X count \r\n\t\t\t\t\tXSCL_CNT &lt;= XSCL_CNT + 1;\r\n\t\t\t\tend if;\r\n\t\t\t\t\r\n\t\t\tend if;\t\r\n\t\t\t\r\n\t\t\t--if on first line, first x, raise frame pulse\r\n\t\t\tif LP_YSCL = '0' AND \r\n\t\t\t\tXSCL = '1' AND\r\n\t\t\t\tXSCL_CNT = 0 and \r\n\t\t\t\tYSCL_CNT = 0 then \r\n\t\t\t\t--raise frame pulse\r\n\t\t\t\tDIN &lt;= '1';\r\n\t\t\telsif LP_YSCL = '0' AND \r\n\t\t\t\t\tXSCL = '1' AND \r\n\t\t\t\t\tXSCL_CNT = 0 AND \r\n\t\t\t\t\tYSCL_CNT = 1 then \r\n\t\t\t\t--shut down frame pulse \r\n\t\t\t\tDIN &lt;= '0';\r\n\t\t\tend if;\r\n\t\t\t\t\r\n\t\t end if; --started\r\n\t\t \t\t\r\n     end if; --clk\t \r\n \t  \r\n   end process;\r\n<\/pre>\n<p>And the most important part, mapping the signals to the output pin.<\/p>\n<pre class=\"brush: vhdl; title: ; notranslate\" title=\"\">\r\n\tA(0) &lt;= LP_YSCL;\r\n\tA(1) &lt;= XSCL;\r\n\tA(5 downto 2) &lt;= UD;\r\n\tA(9 downto 6) &lt;= LD;\r\n\tA(10) &lt;= DIN;\r\n<\/pre>\n<p>I currently don&#8217;t have it setup to connect the display to the FPGA board just yet. I need to wire that up with a 3.3 to 5 logic conversion, since the Spartan3 is a 3.3V part. And looking at the datasheet the minimum &#8220;1&#8221; voltage is .8*VDD and the minimum VDD is 4.75V. Assuming a 5V supply, I need at least 4V to drive these signals. However, I really should verify the output of this before I go through the trouble of hooking it up anyways. Thankfully, the Xilinx tools come with a simulator that can be used to verify your design.<\/p>\n<p><a href=\"http:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/SimulateDesign.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/SimulateDesign-216x300.png\" alt=\"SimulateDesign\" width=\"216\" height=\"300\" class=\"alignnone size-medium wp-image-250\" srcset=\"https:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/SimulateDesign-216x300.png 216w, https:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/SimulateDesign.png 394w\" sizes=\"auto, (max-width: 216px) 100vw, 216px\" \/><\/a><\/p>\n<p>If you click on your source file in the design explore on the left, select Simulation up top, and the right click on &#8216;Simulate Behavioral Model&#8217; and select &#8216;Run&#8217;, you should be see the ISim window open up. For me, the first thing I had to do was setup the clock. Oddly, it runs the simulation first with the clock just set as &#8216;Undefined&#8217;. Under the simulation menu, click &#8216;Restart&#8217; to clear out the current simulation. Right click on the &#8216;clk&#8217; signal, and select &#8216;Force Clock&#8217;. <\/p>\n<p><a href=\"http:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/ForceClockRightClick.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/ForceClockRightClick-219x300.png\" alt=\"ForceClockRightClick\" width=\"219\" height=\"300\" class=\"alignnone size-medium wp-image-251\" srcset=\"https:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/ForceClockRightClick-219x300.png 219w, https:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/ForceClockRightClick.png 403w\" sizes=\"auto, (max-width: 219px) 100vw, 219px\" \/><\/a><\/p>\n<p>Then you enter the parameters for your clock signal. I really wasn&#8217;t sure of the meaning of &#8216;Leading Edge Value&#8217; and &#8216;Trailing Edge Value&#8217; so I entered 1 and 0 respectively as they are required fields. Then I just set the clock period of 31.25ns. <\/p>\n<p><a href=\"http:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/DefineClock.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/DefineClock-291x300.png\" alt=\"DefineClock\" width=\"291\" height=\"300\" class=\"alignnone size-medium wp-image-252\" srcset=\"https:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/DefineClock-291x300.png 291w, https:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/DefineClock.png 358w\" sizes=\"auto, (max-width: 291px) 100vw, 291px\" \/><\/a><\/p>\n<p>Once you&#8217;ve done that, can run that for the amount of time you want to see. These commands should be on the toolbar at the top of the window. <\/p>\n<p><a href=\"http:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/RunForTime.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/RunForTime-300x86.png\" alt=\"RunForTime\" width=\"300\" height=\"86\" class=\"alignnone size-medium wp-image-253\" srcset=\"https:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/RunForTime-300x86.png 300w, https:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/RunForTime.png 539w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Each time you click on the &#8216;Run for Time Specified&#8217; button, it will add to the simulation, so you can verify the signal a few blocks at a time. <\/p>\n<p><a href=\"http:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/LCDSimulation.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/LCDSimulation-300x112.png\" alt=\"LCDSimulation\" width=\"300\" height=\"112\" class=\"alignnone size-medium wp-image-254\" srcset=\"https:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/LCDSimulation-300x112.png 300w, https:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/LCDSimulation-1024x382.png 1024w, https:\/\/burnt-traces.com\/wp-content\/uploads\/2013\/10\/LCDSimulation.png 1116w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Looks good so far. Hopefully soon I can get it wired up and see if it actually works.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A long time ago I picked up an old LCD module (this one, if its still there). Now the trouble with this module is that it does not have a built in controller. It&#8217;s more like a TV or monitor in that it needs to constantly be fed a signal containing the image data to&hellip;&nbsp;<a href=\"https:\/\/burnt-traces.com\/?p=246\" class=\"\" rel=\"bookmark\">Read More &raquo;<span class=\"screen-reader-text\">LCD Module Interface, Part 1<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","footnotes":""},"categories":[3,1],"tags":[29,20,34,38],"class_list":["post-246","post","type-post","status-publish","format-standard","hentry","category-electronics","category-uncategorized","tag-fpga","tag-lcd","tag-papilio","tag-vhdl"],"_links":{"self":[{"href":"https:\/\/burnt-traces.com\/index.php?rest_route=\/wp\/v2\/posts\/246","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/burnt-traces.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/burnt-traces.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/burnt-traces.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/burnt-traces.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=246"}],"version-history":[{"count":6,"href":"https:\/\/burnt-traces.com\/index.php?rest_route=\/wp\/v2\/posts\/246\/revisions"}],"predecessor-version":[{"id":320,"href":"https:\/\/burnt-traces.com\/index.php?rest_route=\/wp\/v2\/posts\/246\/revisions\/320"}],"wp:attachment":[{"href":"https:\/\/burnt-traces.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=246"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/burnt-traces.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=246"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/burnt-traces.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=246"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}