{"id":5630,"date":"2020-01-14T03:52:38","date_gmt":"2020-01-14T03:52:38","guid":{"rendered":"http:\/\/41j.com\/blog\/?p=5630"},"modified":"2021-11-16T01:45:44","modified_gmt":"2021-11-16T01:45:44","slug":"notes-on-using-a-ilx511-linear-ccd-ad9225-adc-ice40hx8k-and-nmigen","status":"publish","type":"post","link":"https:\/\/41j.com\/blog\/2020\/01\/notes-on-using-a-ilx511-linear-ccd-ad9225-adc-ice40hx8k-and-nmigen\/","title":{"rendered":"Notes on using a ILX511 Linear CCD, AD9225 ADC, ICE40HX8K and Nmigen"},"content":{"rendered":"\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/41j.com\/blog\/wp-content\/uploads\/2020\/01\/IMG_9160.jpg\" alt=\"\" class=\"wp-image-5631\" width=\"487\" height=\"364\"\/><\/figure><\/div>\n\n\n\n<p>The messy pile of hacks above is my first attempt at getting an ILX511 Linear CCD working. I&#8217;m using a ICE40HX8K FPGA (Lattice evaluation board). An AD9225 ADC is used to acquire data. An AD823 is used to amplify the signal from the CCD. 2N7002s are used to convert the 3.3v signals to 5v for the CCD. I needed to use 1K pullups to get the required 100ns rise time required&#8230;<\/p>\n\n\n\n<p>The horribly filmed scope trace below shows the ROG signal (resets to start of frame) and amplified Vout.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-vimeo wp-block-embed-vimeo wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Ilx511\" src=\"https:\/\/player.vimeo.com\/video\/384653827?dnt=1&amp;app_id=122963\" width=\"500\" height=\"281\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>The setup is quite sensitive. I had to use a ND1000 (ND 3.0) in front of the filter. In the video above I&#8217;m running a laser pointer back an forth across the CCD, and you can see the peak move relative to the ROG impulse.<\/p>\n\n\n\n<p>The code below is used to drive the CCD. Both the code below and the board are horrible messes. There seem to be a few issues with the code and the output is not as clear as shown in the scope trace. Part of the issue is likely that I&#8217;m trying to write data out to the UART as I&#8217;m acquiring it. I need to buffer one line (or pixels) and then dump it out to the UART&#8230;<\/p>\n\n\n\n<p>But everything more or less works, and it&#8217;s probably time to design a PCB if I want to move forward. I&#8217;ll then have something stable to work with when rewriting the code. A tarball of the code is <a href=\"http:\/\/41j.com\/blog\/wp-content\/uploads\/2020\/01\/ad9225_ilx511.tar.gz\">here<\/a>. A listing is also below, it uses the pll code from kbob (modified for the HX8K on the evaluation board) and uart.py from the nmigen repository.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code brush: plain; notranslate\"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n#!\/usr\/bin\/python3\n\nfrom nmigen import *\nfrom uart import *\nfrom nmigen_boards.ice40_hx8k_b_evn import *\nfrom nmigen.build import *\n\nfrom pll import PLL\n\n\nclass Top(Elaboratable):\n\n    def elaborate(self, platform):\n        # B1 is clock\n        adcclk = &#x5B; Resource(&quot;ad9225clk&quot;, 0, Pins(&quot;B1&quot;, dir=&quot;o&quot;), Attrs(IO_STANDARD=&quot;SB_LVCMOS&quot;)) ]\n\n        adc = &#x5B; Resource(&quot;ad9225&quot;, 0, Pins(&quot;B2 C1 C2 D1 D2 E2 F1 F2 G2 H1 H2 J2&quot;, dir=&quot;i&quot;),  Attrs(IO_STAN\nDARD=&quot;SB_LVCMOS&quot;)) ]\n\n        # pins 3 4 11\n        ilx511shsw = &#x5B; Resource(&quot;ilx511shsw&quot;, 0, Pins(&quot;N3&quot;, dir=&quot;o&quot;), Attrs(IO_STANDARD=&quot;SB_LVCMOS&quot;)) ]\n        ilx511clk  = &#x5B; Resource(&quot;ilx511clk&quot; , 0, Pins(&quot;N2&quot;, dir=&quot;o&quot;), Attrs(IO_STANDARD=&quot;SB_LVCMOS&quot;)) ]\n        ilx511rog  = &#x5B; Resource(&quot;ilx511rog&quot; , 0, Pins(&quot;M2&quot;, dir=&quot;o&quot;), Attrs(IO_STANDARD=&quot;SB_LVCMOS&quot;)) ]\n\n        platform.add_resources(adc)\n        platform.add_resources(adcclk)\n        platform.add_resources(ilx511shsw)\n        platform.add_resources(ilx511clk)\n        platform.add_resources(ilx511rog)\n\n\n        # PLL Stuff\n\n        # If you don't specify dir='-', you will experience a world\n        # of debugging pain.\n        clk_pin = platform.request(platform.default_clk, dir='-')\n\n        m = Module()\n        pll = PLL(freq_in_mhz=12, freq_out_mhz=24)\n\n        m.domains += pll.domain     # override the default 'sync' domain\n\n        timer = Signal(28)\n        timerRog = Signal(28)\n\n        #Connect TX to B12\n        led0     = platform.request('led', 0)\n        muart    = platform.request('uart')\n        data     = platform.request('ad9225',0)\n        dataclk  = platform.request('ad9225clk',0)\n        ccd_shsw = platform.request('ilx511shsw',0)\n        ccd_clk  = platform.request('ilx511clk',0)\n        ccd_rog  = platform.request('ilx511rog',0)\n        dataA = Signal(8)\n        rdy   = Signal(1)\n\n        with m.If(timer&#x5B;8]): #9\n            m.d.sync += &#x5B;\n              rdy.eq(1),\n              timer.eq(0)\n            ]\n        with m.Else():\n            m.d.sync += &#x5B;\n                timer.eq(timer + 1)\n            ]\n\n\n        timerD = Signal(4)\n        m.d.sync += &#x5B;\n            timerD.eq(timerD+1)\n        ]\n        with m.If(rdy == 1):\n            m.d.sync += &#x5B;\n                rdy.eq(0)\n            ]\n\n        with m.If(timerRog&#x5B;15]): #16\n            m.d.sync += &#x5B;\n              ccd_rog.eq(1),\n              timerRog.eq(0),\n              timerD.eq(0),\n              timer.eq(0),\n              dataA.eq(0x0A),\n              rdy.eq(1)\n            ]\n        with m.Else():\n            m.d.sync += &#x5B;\n                timerRog.eq(timerRog + 1),\n                dataA.eq(data.i&#x5B;8:11] + 0x2E),\n            ]\n\n        with m.If(timerRog&#x5B;11]):\n            m.d.sync += &#x5B;\n                ccd_rog.eq(0),\n            ]\n\n\n        with m.If(ccd_rog == 0):\n            m.d.sync += &#x5B;\n                ccd_clk.eq(timerD&#x5B;-1])\n            ]\n        with m.Else():\n            m.d.sync += &#x5B;\n                ccd_clk.eq(0)\n            ]\n\n        m.d.comb += &#x5B;\n            pll.clk_pin.eq(clk_pin),\n            dataclk.eq(timer&#x5B;5])\n        ]\n\n        m.d.comb += &#x5B;\n            ccd_shsw.eq(1) # use Sample and Hold.\n        ]\n\n        # UART\n\n        uart = UART(divisor=208)\n        uart.tx_o = muart.tx\n\n\n        m.d.comb += &#x5B;\n            uart.tx_data.eq(dataA),\n            uart.tx_rdy.eq(rdy)\n        ]\n\n        m.submodules += &#x5B;pll, uart]\n\n        return m\n\n\nif __name__ == '__main__':\n    platform = ICE40HX8KBEVNPlatform()\n\n    platform.build(Top(), do_program=True)\n<\/pre><\/div>","protected":false},"excerpt":{"rendered":"<p>The messy pile of hacks above is my first attempt at getting an ILX511 Linear CCD working. I&#8217;m using a ICE40HX8K FPGA (Lattice evaluation board). An AD9225 ADC is used to acquire data. An AD823 is used to amplify the signal from the CCD. 2N7002s are used to convert the 3.3v signals to 5v for [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[1],"tags":[],"class_list":["post-5630","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1RRoU-1sO","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/posts\/5630","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/comments?post=5630"}],"version-history":[{"count":3,"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/posts\/5630\/revisions"}],"predecessor-version":[{"id":6347,"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/posts\/5630\/revisions\/6347"}],"wp:attachment":[{"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/media?parent=5630"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/categories?post=5630"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/41j.com\/blog\/wp-json\/wp\/v2\/tags?post=5630"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}