<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.3">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2023-02-27T04:53:06+00:00</updated><id>/feed.xml</id><title type="html">Ben Wang’s Blog</title><subtitle>Ben Wang's site with some projects and random thoughts</subtitle><entry><title type="html">Phased Array Microphone</title><link href="/2023/02/26/Phased-Array-Microphone.html" rel="alternate" type="text/html" title="Phased Array Microphone" /><published>2023-02-26T00:00:00+00:00</published><updated>2023-02-26T00:00:00+00:00</updated><id>/2023/02/26/Phased-Array-Microphone</id><content type="html" xml:base="/2023/02/26/Phased-Array-Microphone.html">&lt;p&gt;A 192-channel phased array microphone, with FPGA data acquisition and beamforming/visualization on the GPU.
Phased arrays allow for applications not possible with traditional directional microphones, as the directionality
can be changed instantly, after the recording is made, or even be focused at hundreds of thousands of points 
simultaneously in real time.&lt;/p&gt;

&lt;p&gt;All designs are open source:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/kingoflolz/mic_host&quot;&gt;Host software&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/kingoflolz/mic_gateware&quot;&gt;FPGA gateware&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/kingoflolz/mic_hardware&quot;&gt;PCB layout and schematics, mechanical components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/mic block diagram.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt; Block diagram &lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/mic overall.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt; Glamor shot &lt;/p&gt;

&lt;h2 id=&quot;hardware&quot;&gt;Hardware&lt;/h2&gt;

&lt;p&gt;To create a phased array microphone, a large number of microphones needs to be placed in an arrangement with
 a wide distribution of spacing. For a linear array, exponential spacing between microphones is found to be optimal for
 broadband signals. To create a 2d array, these symmetrical linear arrays (“arms”) are be placed radially, which allows
the central (“hub”) board to be compact. The total cost for the array is approximately $700.&lt;/p&gt;

&lt;h3 id=&quot;arms&quot;&gt;Arms&lt;/h3&gt;

&lt;p&gt;The length of each arm is dictated by the limits of PCB manufacturing and assembly. These boards were made at JLCPCB,
where the maximum length for manufacturing and assembly of 4 layer PCBs was 570mm.&lt;/p&gt;

&lt;p&gt;The microphones chosen were the &lt;a href=&quot;https://www.lcsc.com/product-detail/MEMS-Microphones_MEMS-MSM261D4030H1CPM_C966942.html&quot;&gt;cheapest digital output MEMS microphone&lt;/a&gt;
(because there are a lot of them!), which were about $0.5. At this bottom of the barrel price
range, there is little differentiation in the performance characteristics between different microphones. Most have 
decent performance up to 10khz and unspecified matching of phase delay and volume.&lt;/p&gt;

&lt;p&gt;These microphones output data using pulse density modulation (PDM), which provides a one bit output at a frequency
significantly higher than the audible range (up to 4 MHz), with the high sampling rate compensating for quantization noise. 
These microphones also support latching the data either on the rising or falling edge of the clock (DDR), which allows
two microphones to be multiplexed on a single wire, reducing the amount of connections required.&lt;/p&gt;

&lt;p&gt;Each arm contains 8 microphones sharing 4 output lines, as well as an output buffer on the clock input line.
This ensures the rise times are reasonable, even with hundreds of microphones sharing the same clock signal.&lt;/p&gt;

&lt;p&gt;For some reason (likely the low rigidity of the panel and some suboptimal solder paste stencil patterns combined with 
the LGA microphone footprints), the yields on the arm PCBs are not very good, with only 50% of them working out of the 
box. The most common fault was the clock line being shorted to either 3V3 or ground, which unfortunately requires trial
and error of removing microphones from the PCB until the short is resolved. Next time some series resistors on the
clock line would speed this process up a lot, and improving the panelization and paste stencil would likely 
improve yields so extensive rework isn’t required.&lt;/p&gt;

&lt;p&gt;Even with rework, there are still some microphones which produce bogus data. These are just masked out in the code, as
there are enough working ones to make up for it (and it’s too much work to remove a bunch of arms to do more rework…)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/mic arm panel.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;hub&quot;&gt;Hub&lt;/h3&gt;

&lt;p&gt;An FPGA is used to collect all the data, due to the large number of low latency IOs available combined with the ability 
to communicate using high speed interfaces (e.g. Gigabit Ethernet). Specifically, the &lt;a href=&quot;https://www.colorlight-led.com/product/colorlight-i5-led-display-receiver-card.html&quot;&gt;Colorlight i5&lt;/a&gt;
card is used, as it has enough IOs, is cheap and readily available, and has two integrated ethernet PHYs 
(only one is used for this project). The card is originally designed as an ethernet interface for LED panels, but has 
been &lt;a href=&quot;https://github.com/wuxx/Colorlight-FPGA-Projects&quot;&gt;fully reverse engineered&lt;/a&gt;. About 100 GPIOs are broken out over 
the DDR2 connector, which is much easier to fan out than the BGA of the original FPGA.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/mic hub board.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Other than the FPGA, the hub contains some simple power management circuitry, and connectors for the arm boards as well
as an Ethernet connector with integrated magnetics.&lt;/p&gt;

&lt;h3 id=&quot;mechanical-design&quot;&gt;Mechanical Design&lt;/h3&gt;

&lt;p&gt;The arms are attached with M3 screws to the hub using &lt;a href=&quot;https://www.lcsc.com/product-detail/Nuts_Sinhoo-SMTSO3080CTJ_C2916369.html&quot;&gt;PCB mounted standoffs/nuts&lt;/a&gt; 
, which conveniently can be assembled with SMD processes. The connections from each arm to the hub is made with 8 pin,
2mm pitch connectors.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/mic hub attachment.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The original mechanical design consists of slots on the arm PCBs which interlock with circumferential structural PCBs,
however the low torsional rigidity of the arms means the whole structure deformed too easily.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/mic structural pcb.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The final mechanical design consists of pieces of laser cut 1/4th inch MDF around the outer edge of the array, with each
arm attached to the MDF with some zip ties.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/mic arm attachment.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As the microphone array is mounted on the wall (which is very susceptible to reflections), a layer of acoustic foam is
used to attenuate the reflections to make calibration easier.&lt;/p&gt;

&lt;h2 id=&quot;gateware&quot;&gt;Gateware&lt;/h2&gt;

&lt;p&gt;The main objective for the gateware is to reliably transmit the raw acquired data losslessly to the computer for 
further processing, while keeping it as simple as possible. Performing decimation and filtering on the
FPGA would reduce the data rate, but sending the raw PDM data is achievable with Gigabit Ethernet. This
reduces the complexity of the FPGA code and allowing faster iteration. Compiling is much faster than place and route,
and it’s much easier to use a debugger in code than in gateware!&lt;/p&gt;

&lt;p&gt;There are three major components to the gateware, a module for interfacing with the PDM interfaces, a module for 
creating fixed size packets from those readings, and a UDP streamer to write the packets to the Ethernet interface.&lt;/p&gt;

&lt;h3 id=&quot;pdm-interface&quot;&gt;PDM Interface&lt;/h3&gt;

&lt;p&gt;The PDM input module is a relatively simple piece of logic, which divides the 50 MHz system clock by a factor of 16 to 
output a 3.125MHz PDM clock, latches all 96 of the input pins after each clock edge, and then shifts out 32 bits of the 
data on each clock cycle. Each chunk of 192 bits is has a header added which is a 32 bit incrementing integer.&lt;/p&gt;

&lt;p&gt;The PDM interface receives data at a rate of 3.125Mhz * 96 (input pins) * 2 (DDR), which is 600Mbps. With the header,
the data rate output from this module is 700Mbps, or approximately 40% utilization of the 32 bit output data path.&lt;/p&gt;

&lt;h3 id=&quot;packetizer&quot;&gt;Packetizer&lt;/h3&gt;

&lt;p&gt;The packetizer is essentially a FIFO buffer with a special interface on the input. A standard FIFO marks the output as available
whenever there is at least one item in the queue, but this would lead to smaller packets than requested as the ethernet
interface operates faster than the PDM output (leading to buffer underruns).
Thus, the packetizer waits until there is at least a full packet worth of 
data in the queue before starting a packet, which ensures constant sized packets.&lt;/p&gt;

&lt;p&gt;48 PDM output blocks at 224 bits (192 bits of data with a 32 bit header) are placed into each packet, which totals
1344 bytes of data per packet, plus a 20 byte IPv4 header and an 8 byte UDP header, at a rate of approximately 65k pps.&lt;/p&gt;

&lt;p&gt;This leads to a wire rate of 715 Mbps, or about 70% utilization of Gigabit Ethernet.&lt;/p&gt;

&lt;h3 id=&quot;udp-streamer&quot;&gt;UDP Streamer&lt;/h3&gt;

&lt;p&gt;The LiteEth project made this module very easy, as it abstracts out the lower level complexities of UDP and IP 
encapsulation, ARP tables and the like, and provides a convenient interface for simply hooking up a FIFO to a UDP 
stream. Occasionally there is some latency, but there is enough slack in the bus and buffer in the
packetizer FIFO to absorb any hiccups.&lt;/p&gt;

&lt;h3 id=&quot;utilization-and-timing&quot;&gt;Utilization and Timing&lt;/h3&gt;

&lt;p&gt;The FPGA on the Colorlight i5 is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LFE5U-25F-6BG381C&lt;/code&gt;, which has 25k LUTs. The design is 
placed and routed with the open source Project Trellis toolchain. By keeping the gateware very simple, 
the utilization on the device is quite low, and there is lots of room for additional functionality.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Info: Device utilisation:
Info:                 DP16KD:    16/   56    28%
Info:                EHXPLLL:     1/    2    50%
Info:             TRELLIS_FF:  1950/24288     8%
Info:           TRELLIS_COMB:  3701/24288    15%
Info:           TRELLIS_RAMW:    49/ 3036     1%

Info: Max frequency for clock                   '$glbnet$crg_clkout': 73.17 MHz (PASS at 50.00 MHz)
Warning: Max frequency for clock '$glbnet$eth_clocks1_rx$TRELLIS_IO_IN': 124.07 MHz (FAIL at 125.00 MHz)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Timing violations on eth rx clock is due to &lt;a href=&quot;https://github.com/litex-hub/litex-boards/issues/40#issuecomment-1108817182&quot;&gt;false positive from gray counter in liteeth&lt;/a&gt;)&lt;/p&gt;

&lt;h2 id=&quot;software&quot;&gt;Software&lt;/h2&gt;

&lt;h3 id=&quot;cic-filter&quot;&gt;CIC Filter&lt;/h3&gt;

&lt;p&gt;Each microphone produces a 1 bit signal at 3.125Mhz, and needs to be reduced to a more reasonable sample rate
and bit depth for further
processing. This is done very efficiently with a CIC filter, which only requires a few arithmetic operations to 
process each sample. For understanding more about CIC filters, &lt;a href=&quot;https://tomverbeure.github.io/2020/09/30/Moving-Average-and-CIC-Filters.html&quot;&gt;this series&lt;/a&gt;
of blog posts from Tom Verbeure provides an excellent introduction. Following the nice graphs from there, 
I decided on a 4 stage, 16x decimation CIC filter which reduced the sample rate to a much reasonable 195kHz, at 32 bits.&lt;/p&gt;

&lt;p&gt;To ingest the data at 3.125Mhz, the filter must be able to process each set of samples in 320ns. A naive implementation
in Rust wasn’t fast enough on a single core, but an implementation with some less abstraction (and a hence some more 
autovectorization) got there, and is what was used at the end. I also experimented with a version using SIMD 
intrinsics which was much faster, but ended up running into alignment issues when using it in together with other code.&lt;/p&gt;

&lt;p&gt;Even with close to a billion bits per second of data to process, a single CPU core can do quite a few operations on 
each individual bit!&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;test cic::bench_cic       ... bench: 574 ns/iter (+/- 79) = 41 MB/s
test cic::bench_fast_cic  ... bench: 181 ns/iter (+/- 24) = 132 MB/s
test cic::bench_simd_cic  ... bench:  36 ns/iter (+/- 0)  = 666 MB/s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;calibration&quot;&gt;Calibration&lt;/h3&gt;

&lt;p&gt;To perform array calibration, a speaker playing white noise is moved around the room in front of the array. An FFT based
cross correlation is performed between all pairs of microphones to compute relative delays.&lt;/p&gt;

&lt;p&gt;A cross correlation can be
performed by computing the FFT of both signals (cached and computed once for each signal), and then computing the 
inverse FFT of the complex multiplication of the two. This is quite compute intensive, as there are over 18 thousand
pairs! For the window sizes used of 16-64k, the FFTs are memory bound, and thus the IFFT and peak finding is fused to
avoid writing the results to memory, which results in a 15x speedup. On a 7950X, this process runs in realtime.&lt;/p&gt;

&lt;p&gt;Then the positions of the source at each timestep 
and the positions of each microphone is optimized using gradient descent (when you know PyTorch, all optimization 
problems look like gradient descent…). The loss function tries to minimize the difference between the measured
correlations and the ideal correlations, while trying to minimize the deviation of the microphone positions from
the initial positions as well as the jerk of the source trajectory.&lt;/p&gt;

&lt;p&gt;As part of the calibration, the speed of sound is also a parameter which is optimized to obtain the best model of the
system, which allows this whole procedure to act as a ridiculously overengineered thermometer.&lt;/p&gt;

&lt;p&gt;After a few hundred iterations, it converges to a reasonable solution for both
the source positions and the microphone positions, as well as constants such as the speed of sound. Fortunately this
problem vectorizes well for GPU, and converges in a few seconds.&lt;/p&gt;

&lt;p&gt;The final mean position error is on the order of 1mm, and is able to correct for large scale systematic distortions
such as concavity from the lack of structural rigidity. The largest position error between the calibrated positions
and the designed positions is on the order of 5mm, which is enough to introduce significant phase errors to high 
frequency sound if uncorrected, although perhaps not strictly necessary (10khz sound has a wavelength of ~3.4cm).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/mic calibration.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;beamforming&quot;&gt;Beamforming&lt;/h3&gt;

&lt;p&gt;Beamforming is how the raw microphone inputs are processed to produce directional responses. The simplest method of
beamforming is delay-and-sum (DAS), where each signal is delayed according to its distance from the source. This is 
the type of beamforming implemented for this process, with the beamforming happening in the frequency domain.&lt;/p&gt;

&lt;p&gt;In the frequency domain, a delay can be implemented by the complex multiplication of the signal with a linear phase term
proportional to the delay required, which also nicely handles delays which are not integer multiples of the sampling 
period.&lt;/p&gt;

&lt;p&gt;Multiple nested subarrays of the original array are used for different frequency ranges. This reduces the processing
required for beamforming, as each frequency does not need to be beamformed with all the microphone. This also ensures
that the beamforming gains of all the frequencies are matched.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/mic subarray.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Two different types of beamforming visualizations are implemented, a 3d near field beamformer and a 2d far field
beamformer. When the audio source is far away, the wavefront is essentially a flat plane, and how far away the
source is does not meaningfully change the signals at the array. On the other hand, if the source is close to the 
array, the wavefront will have a significant curvature which allows the 3d location of the source to be determined.&lt;/p&gt;

&lt;p&gt;The beamformer is implemented as a &lt;a href=&quot;https://github.com/openai/triton/&quot;&gt;Triton kernel&lt;/a&gt;, a Python DSL which compiles to 
run on Nvidia GPUs. When beamforming to hundreds of thousands of points, the massive parallelism provided
by GPUs allows for results to be produced in real time. Some &lt;a href=&quot;https://github.com/openai/triton/issues/974&quot;&gt;current limitations&lt;/a&gt;
with the Triton language around 
support to indexing with shared memory arrays lead to slightly suboptimal performance, but writing CUDA C++ doesn’t 
seem very fun…&lt;/p&gt;

&lt;h4 id=&quot;near-field-3d-beamforming&quot;&gt;Near Field 3D Beamforming&lt;/h4&gt;

&lt;p&gt;Near field 3D beamforming is performed a 5cm voxel grid with size 64x64x64. An update rate of 12hz is achieved on a
RTX 4090 with higher update rates limited by overhead of suboptimal CPU-GPU synchronization with the smaller work units. 
The voxel grid is then visualized using &lt;a href=&quot;https://vispy.org/&quot;&gt;VisPy&lt;/a&gt;,
a high performance data visualization library which uses OpenGL. Modern games have millions of polygons, so rendering
a quarter million semi-transparent voxels at interactive framerates is no issue.&lt;/p&gt;

&lt;p&gt;A quick demo of the voxel visualization below, note the reflection off the roof!&lt;/p&gt;

&lt;video controls=&quot;&quot; style=&quot;max-width: 600px; outline: none&quot;&gt;
&lt;source src=&quot;/assets/mic%203d%20demo.mp4&quot; type=&quot;video/mp4&quot; /&gt;
Your browser does not support the video tag.
&lt;/video&gt;

&lt;h4 id=&quot;far-field-2d-beamforming&quot;&gt;Far Field 2D Beamforming&lt;/h4&gt;

&lt;p&gt;Far field beamforming works similarly, but can be performed in higher resolution as there is no depth dimension
required. A 512x512 pixel grid is used, and the same 12hz update rate achieved. (The far field beamforming uses an 
approximation of just putting the points far away instead of actually assuming planar wavefront due to laziness…)&lt;/p&gt;

&lt;p&gt;A demo of the 2d visualization here, but it’s not very exciting due to the poor acoustic environment of my room around 
the array, with lots of reflections and multipath.&lt;/p&gt;

&lt;video controls=&quot;&quot; style=&quot;max-width: 600px; outline: none&quot;&gt;
&lt;source src=&quot;/assets/mic%202d%20demo.mp4&quot; type=&quot;video/mp4&quot; /&gt;
Your browser does not support the video tag.
&lt;/video&gt;

&lt;h4 id=&quot;directional-audio&quot;&gt;Directional Audio&lt;/h4&gt;

&lt;p&gt;The previous two beamforming implementations compute the energy of sound from each location, but never materializes
the beamformed audio in memory. A time domain delay and sum beamformer is implemented to allow for directional audio 
recording. It takes a 3D coordinate relative from array center and outputs audio samples. 
An interesting aspect about this beamformer is that it is differentiable with regard to the location from the output. 
This means the location of the audio sources can be optimized
based on some differentiable loss function (like neural network), which might allow for some interesting applications 
such as using a forced alignment model of a multi-party transcript to determine the physical location of each speaker.&lt;/p&gt;

&lt;p&gt;A speaker playing some audio is placed in front of the array, with another speaker placed approximately 45 degrees away
at the same distance from array center, playing white noise. The effectiveness of the beamforming can be demonstrated
by comparing the raw audio from a single microphone with the output from the beamforming.&lt;/p&gt;

&lt;p&gt;Raw audio from a single microphone:&lt;/p&gt;

&lt;audio controls=&quot;&quot; style=&quot;max-width: 600px; outline: none&quot;&gt;
&lt;source src=&quot;/assets/mic%20raw.wav&quot; type=&quot;audio/wav&quot; /&gt;
Your browser does not support the video tag.
&lt;/audio&gt;

&lt;p&gt;Beamformed audio:&lt;/p&gt;

&lt;audio controls=&quot;&quot; style=&quot;max-width: 600px; outline: none&quot;&gt;
&lt;source src=&quot;/assets/mic%20beamformed.wav&quot; type=&quot;audio/wav&quot; /&gt;
Your browser does not support the video tag.
&lt;/audio&gt;

&lt;h3 id=&quot;recording&quot;&gt;Recording&lt;/h3&gt;

&lt;p&gt;As the data from the microphone array is just UDP packets, it can be recorded with tools like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tcpdump&lt;/code&gt;, and the 
packet capture file can be read to reinject the packets back into the listener. All the programs in the
previous sections are designed to work at real time, but can also work on recorded data using this process.&lt;/p&gt;

&lt;p&gt;The tradeoff with this recording implementation is that the output data rate is quite high (due to faithfully recording
everything, even the quantization noise). At 87.5 MBps, a 1-hour recording would be 315 GB! A more optimized
implementation would do some compression, and do the recording after the CIC filter at a lower sample rate.&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;

&lt;p&gt;I consider this project essentially complete, and don’t plan to work on it any further for the foreseeable future, but
there are still lots of possible cool extensions if you’d like to build one!&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Using more advanced beamforming algorithms (&lt;a href=&quot;https://ntrs.nasa.gov/api/citations/20080015889/downloads/20080015889.pdf&quot;&gt;DAMAS&lt;/a&gt; etc.)&lt;/li&gt;
  &lt;li&gt;Better GUI to combine all existing functions (e.g. See where sound is coming from, and record audio from there)&lt;/li&gt;
  &lt;li&gt;Combine differentiable beamforming and neural models (e.g. forced alignment example mentioned above)&lt;/li&gt;
&lt;/ul&gt;</content><author><name></name></author><summary type="html">A 192-channel phased array microphone, with FPGA data acquisition and beamforming/visualization on the GPU. Phased arrays allow for applications not possible with traditional directional microphones, as the directionality can be changed instantly, after the recording is made, or even be focused at hundreds of thousands of points simultaneously in real time.</summary></entry><entry><title type="html">PCB Linear Actuator</title><link href="/2022/08/09/PCB-Linear-Actuator.html" rel="alternate" type="text/html" title="PCB Linear Actuator" /><published>2022-08-09T00:00:00+00:00</published><updated>2022-08-09T00:00:00+00:00</updated><id>/2022/08/09/PCB-Linear-Actuator</id><content type="html" xml:base="/2022/08/09/PCB-Linear-Actuator.html">&lt;p&gt;&lt;img src=&quot;/assets/linear%20motor.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A fully integrated PCB linear actuator, with the motor driver, encoder and motor all on a single PCB. All designs are
open source:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/kingoflolz/linear-actuator-firmware&quot;&gt;Firmware and host software&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/kingoflolz/linear-actuator-hardware&quot;&gt;PCB layout and schematics, mechanical components, simulation code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A video of it in action:&lt;/p&gt;

&lt;video controls=&quot;&quot; style=&quot;max-width: 600px; outline: none&quot;&gt;
&lt;source src=&quot;/assets/linear%20video.mp4&quot; type=&quot;video/mp4&quot; /&gt;
Your browser does not support the video tag.
&lt;/video&gt;

&lt;h1 id=&quot;motor-design-and-validation&quot;&gt;Motor Design and Validation&lt;/h1&gt;

&lt;p&gt;Design parameters of the motor were simulated with &lt;a href=&quot;https://www.femm.info/wiki/HomePage&quot;&gt;FEMM&lt;/a&gt;. There are not too many
free parameters to optimize, but the goal is to find some off the shelf magnets which can be used, and to estimate the
importance of things like air gap on motor performance.&lt;/p&gt;

&lt;p&gt;The design ended up having the simplest possible winding layout (3 slots per pole), which maximized the amount of
current carrying traces, while also having a high winding factor. 50mm * 10mm * 2mm magnets were used (which are a bit
smaller in reality by ~0.5mm in each dimension), as they were easily available on
&lt;a href=&quot;https://www.ebay.com/itm/283456396736&quot;&gt;EBay&lt;/a&gt; etc.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/linear%20flux.png&quot; alt=&quot;Picture of the magnetics simulation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Flux visualization of magnetics simulation.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/linear%20force.png&quot; alt=&quot;Force outputs of magnetic simulation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Force outputs of magnetic simulation. The force is very linear with respect to current, as there is no iron core to 
saturate. The simulated force seems reasonably accurate compared to the measured force, with likely the biggest
discrepancy coming from the questionable quality of the magnets.&lt;/p&gt;

&lt;p&gt;The motor itself is constructed using a 4 layer 2oz PCB (with 2oz inner layers) from JLCPCB. The motor itself uses all
4 layers to effectively get 8 oz of copper. At the end turns, each phase uses a single layer, with the last one used
as the star point.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/linear%20motor.png&quot; alt=&quot;Picture of motor layout&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/linear%20power.png&quot; alt=&quot;Power output simulation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It appears the empirical power dissipation is approximately twice as high as the estimates. Not entirely sure why, but
my best guess is a combination of copper being slightly thinner than 75um per layer in reality, as well as the power
dissipation of the drivers.&lt;/p&gt;

&lt;p&gt;There is space to attach a small heatsink on top of the PCB, and a continuous power dissipation of 25W doesn’t seem to
cause any issues.&lt;/p&gt;

&lt;h1 id=&quot;motor-assembly&quot;&gt;Motor Assembly&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/assets/linear%20motor%20overview.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/linear%20cross%20section.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The motor is constructed with two mild steel plates, sandwiching an aluminium spacer. Parts were made at SendCutSend,
which was quite fast and reasonably priced. The magnets (not shown in CAD) are simply just placed on the steel plates.
No additional attachment required. Assembly is quite tricky due to the strength of the magnet, but can be done with
some non-magnetic spacers (I used scrap FR4 PCBs).&lt;/p&gt;

&lt;p&gt;The linear rail is mounted to the bottom plate with a 20mm x 20mm aluminium extrusion. The motor PCB is attached to the
rail blocks with a 3d printed mounting block.&lt;/p&gt;

&lt;h1 id=&quot;motor-driver&quot;&gt;Motor Driver&lt;/h1&gt;

&lt;p&gt;I used smart power stage ICs (also known as DrMOS) for the power stage (specifically the FDMF3170). This is a bit
of an unconventional use for these chips, but it integrates the high side and low side MOSFETs, both gate drivers, 
temperature and current sensing and bootstrap circuitry into a single chip which only requires a few passives. This
made the power stage layout a breeze, and also allows for operation at very high switching frequencies (up to 1Mhz).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/fdmf3170.png&quot; alt=&quot;Block diagram of FDMF3170 power stage&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Block diagram of the FDMF3170 power stage. You really get quite a lot for &lt;a href=&quot;https://www.lcsc.com/product-detail/Gate-Drive-ICs_onsemi-FDMF3170_C890998.html&quot;&gt;just a few bucks&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/linear%20power%20stage.png&quot; alt=&quot;Power stage layout&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The power stage layout, which is now pretty boring as each phase is just a single chip.&lt;/p&gt;

&lt;p&gt;However, the top and bottom FETs are not balanced, with the bottom side having much lower RDSon than 
the top side (These are designed for 12V to ~1V switching converters after all). This means the SVM algorithm needs to 
be slightly modified, away from the traditional alt-rev SVM (where during the zero period, the 111 and 000 phase vectors
are both used alternately to balance heat dissipation between the top and bottom sides, under the assumption that the
switches are the same). The modified scheme only uses the 000 phase vector, which minimizes resistance losses.&lt;/p&gt;

&lt;p&gt;An additional challenge of driving this type of linear motor is that the inductance of the motor is very low, as there
is no iron core. This would necessitate an extremely high switching frequency. I decided not to do this (mostly due to
running out of timer resolution), and instead added a small inductor (470nH) in series with the motor leads to increase
the inductance. This increases the resistance losses, but it is quite small.&lt;/p&gt;

&lt;p&gt;The driver is controlled by a STM32F412 microcontroller, and switches at 200khz with the control loop running at 8khz.&lt;/p&gt;

&lt;h1 id=&quot;encoder&quot;&gt;Encoder&lt;/h1&gt;

&lt;p&gt;The encoder is a reflective IR sensor with 8 channels. 4 channels are used to build a quadrature encoder, with
differential signals for the sin and cosine phases. The other 4 channels are for 2 other non-differential quadrature
tracks with different frequencies. This theoretically enables for absolute positional encoding (as the phase multiple
can be computed using the phase difference between the 3 quadrature signals), but I haven’t implemented this yet.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/linear%20quadrature.png&quot; alt=&quot;Encoder quadrature signals&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The signals are a bit messy due to some crosstalk between channels, as well as some switching noise, but achieves 
approximately 10um peak to peak noise after decoding.&lt;/p&gt;

&lt;h1 id=&quot;embedded-software&quot;&gt;Embedded Software&lt;/h1&gt;

&lt;p&gt;Firmware is written entirely in Rust, using the &lt;a href=&quot;https://rtic.rs/1/book/en/&quot;&gt;RTIC framework&lt;/a&gt;. The main task is driven
by a 8khz timer interrupt, which triggers an ADC scan and DMA requests to read all encoder and motor driver inputs.
The DMA completion interrupt triggers a software task which computes the control loop, which sets the PWM duty cycles.
A background task handles the USB communications, which uses any remaining CPU time.&lt;/p&gt;

&lt;p&gt;One interesting feature of the firmware is in the communication protocol. Rather than hand coding getter and setter
methods for each parameter, I wrote a crate called &lt;a href=&quot;https://github.com/kingoflolz/remote-obj&quot;&gt;remote-obj&lt;/a&gt; to essentially
create getter and setters automatically for nested structs and enums using a proc-macro.&lt;/p&gt;

&lt;p&gt;Combined with a way to &lt;a href=&quot;https://github.com/kingoflolz/remote-obj#dehydration&quot;&gt;compactly encode the parameters with synchronized metadata&lt;/a&gt;
between the host and device, this enables an on-device oscilloscope which can sample variables at up to the
control loop frequency (up to 16 values at 8khz). The data is buffered on-device using a neat
&lt;a href=&quot;https://crates.io/crates/bbqueue&quot;&gt;lockless ring buffer&lt;/a&gt; which holds packets contiguously in memory for easy access.&lt;/p&gt;

&lt;p&gt;This data is then sent to the host over USB CDC ACM. Even though there is no latency/throughput guarantee when using 
USB bulk endpoints, I’ve found it to be quite fast and low latency, reliably achieving up to 8 mbps out of the 
theoretical 12 mbps of the USB full speed PHY built in to the STM32, while also having &amp;lt;10ms of latency.&lt;/p&gt;

&lt;h1 id=&quot;host-software&quot;&gt;Host Software&lt;/h1&gt;

&lt;video controls=&quot;&quot; style=&quot;max-width: 600px; outline: none&quot;&gt;
&lt;source src=&quot;/assets/linear%20gui.mp4&quot; type=&quot;video/mp4&quot; /&gt;
Your browser does not support the video tag.
&lt;/video&gt;

&lt;p&gt;The host software is also written in Rust, using the &lt;a href=&quot;https://crates.io/crates/egui&quot;&gt;egui framework&lt;/a&gt;. Most of the 
complexity is involved with handling the USB communication in a low latency way. In particular, dedicated threads drive 
reading from and writing to the USB endpoints.&lt;/p&gt;

&lt;p&gt;To create a low latency USB stream, the easiest way is to continuously send read requests in a loop, and have the USB
device terminate the transfer when the device runs out of buffer (using a non-full packet). Importantly to have high
throughput is to immediately send another read request when another completes, which keeps the bus busy. The results
are communicated over channels to other threads.&lt;/p&gt;

&lt;p&gt;The software uses runtime reflection facilities provided by remote-obj to view and set parameters (and to know what
parameters are available to view and set).&lt;/p&gt;

&lt;h1 id=&quot;performance&quot;&gt;Performance&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/assets/linear%20position.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;100mm moves take under 200ms. (each horizontal tick is 100ms)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/linear%20velocity.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;From the velocity plot above, it takes approximately 60ms to achieve 2100mm/s, for an acceleration of around 35m/s^2,
and it takes approximately 40ms to decelerate back to 100mm/s, for deceleration of around 50m/s^2.&lt;/p&gt;

&lt;p&gt;Better performance can likely be achieved with higher currents, but I’m limited by the quite low OCP of my power supply
(which is a macbook charger…)&lt;/p&gt;

&lt;h1 id=&quot;previous-versions-and-future&quot;&gt;Previous Versions and Future&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/assets/linear%20history.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It took a few hardware revisions to get to this point, and I’ll probably do one more before I call this project “done”&lt;/p&gt;

&lt;p&gt;Get in touch if you would like a few assembled boards from the next revision! (contact info in the footer)&lt;/p&gt;</content><author><name></name></author><summary type="html"></summary></entry><entry><title type="html">Custom Axial Flux Motor</title><link href="/2020/07/10/AFM.html" rel="alternate" type="text/html" title="Custom Axial Flux Motor" /><published>2020-07-10T00:00:00+00:00</published><updated>2020-07-10T00:00:00+00:00</updated><id>/2020/07/10/AFM</id><content type="html" xml:base="/2020/07/10/AFM.html">&lt;p&gt;This personal project started in 2018 as a way to experiment with electromagnetics simulation and prototyping. A set of relatively arbitrary constraints and goals (~200g, ~60mm diameter, maximise low speed torque) were chosen as this project was primarily an exploratory project with no immediate application.&lt;/p&gt;

&lt;p&gt;With these constraints, magnetics simulations were run with a python script, which using gradient descent and simulated annealing, optimized the topology and parameters of the motor with respect to the torque, efficiency, weight and estimated manufacturing cost.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/afm%20flux.png&quot; alt=&quot;Picture of the unwrapped magnetics simulation (one of many!)&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When the optimized design was produced, the models were drawn up in CAD, and these drawings were then sent to be manufactured in China, including milled carbon fiber, custom neodymium magnets, and electric discharge machined magnetic steel.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/afm%20cad.png&quot; alt=&quot;Picture of the motor in CAD (OnShape)&quot; /&gt;
&lt;img src=&quot;/assets/afm%20irl.png&quot; alt=&quot;Picture of the assembled motor&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The assembled motor was then tested, and performed within 20% of the simulations, which validated the design and prototyping process for custom motors. Furthermore, the motor constant obtained was approximately 2x higher than motors of comparable weight and diameter, with higher continuous torque and efficiency.&lt;/p&gt;</content><author><name></name></author><summary type="html">This personal project started in 2018 as a way to experiment with electromagnetics simulation and prototyping. A set of relatively arbitrary constraints and goals (~200g, ~60mm diameter, maximise low speed torque) were chosen as this project was primarily an exploratory project with no immediate application.</summary></entry><entry><title type="html">LFTag</title><link href="/2020/07/10/LFTag.html" rel="alternate" type="text/html" title="LFTag" /><published>2020-07-10T00:00:00+00:00</published><updated>2020-07-10T00:00:00+00:00</updated><id>/2020/07/10/LFTag</id><content type="html" xml:base="/2020/07/10/LFTag.html">&lt;p&gt;&lt;img src=&quot;/assets/lftageg.png&quot; alt=&quot;Picture of LFTags&quot; /&gt;&lt;/p&gt;

&lt;p&gt;LFTag is a new visual fiducial standard, similar to AprilTag, ArUco and ARTag, designed to maximize the use of Low spacial Frequencies (hence the name), and significantly advances the state of the art in dictionary size-detection range tradeoffs for visual fiducial markers. It was a independing research project completed in the summer of 2020, and will be presented at IEEE CVIV 2020.&lt;/p&gt;

&lt;p&gt;A preprint is available &lt;a href=&quot;https://arxiv.org/abs/2006.00842&quot;&gt;here&lt;/a&gt;, and the conference presentation slide deck is available &lt;a href=&quot;https://docs.google.com/presentation/d/1EZozKKGBuTL88Qhh3uacIZlN6TsmGbYOhnvt1Oosy3M/edit?usp=sharing&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The gist of the algorithm consists of topological connected component analysis to find potential markers in the image, with data encoded within the marker using the relative positioning of marker elements.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/lftag.png&quot; alt=&quot;Picture of LFTag&quot; /&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html"></summary></entry><entry><title type="html">MOC900X</title><link href="/2020/07/10/MOC900x.html" rel="alternate" type="text/html" title="MOC900X" /><published>2020-07-10T00:00:00+00:00</published><updated>2020-07-10T00:00:00+00:00</updated><id>/2020/07/10/MOC900x</id><content type="html" xml:base="/2020/07/10/MOC900x.html">&lt;p&gt;MOC900X is a series of high power density motor drivers which I am designing for the University of Pennsylvania’s Formula SAE Electric team, &lt;a href=&quot;http://www.pennelectricracing.com/&quot;&gt;Penn Electric Racing&lt;/a&gt;. This project was started in late 2019, and is currently ongoing.&lt;/p&gt;

&lt;p&gt;The goal of this project is to design a high voltage (~600V), high power (~60kW) motor controller for the 2021 season, with strong priorities placed on weight and efficiency. Due to these goals, high performance &lt;a href=&quot;https://gansystems.com/gan-transistors/gs66516b/&quot;&gt;GaN transistors&lt;/a&gt; were selected and evaluated on a custom insulated metal substrate half bridge test board controlled by an FPGA:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/MOC900X%20photo.png&quot; alt=&quot;Photo of the half bridge test board&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The half bridge design has been validated both electrically and thermally, and performs at over 99% efficiency at 500V, 100A. The current design iteration puts three half bridges together, and is on target to achieve extremely high whole system specific power, on the order of 170kW/kg including cooling, enclosure and connectors&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/MOC900X%20diagram.png&quot; alt=&quot;CAD shot of the 3 phase test board currently in development&quot; /&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">MOC900X is a series of high power density motor drivers which I am designing for the University of Pennsylvania’s Formula SAE Electric team, Penn Electric Racing. This project was started in late 2019, and is currently ongoing.</summary></entry><entry><title type="html">RemoteBoot</title><link href="/2020/07/10/RemoteBoot.html" rel="alternate" type="text/html" title="RemoteBoot" /><published>2020-07-10T00:00:00+00:00</published><updated>2020-07-10T00:00:00+00:00</updated><id>/2020/07/10/RemoteBoot</id><content type="html" xml:base="/2020/07/10/RemoteBoot.html">&lt;p&gt;RemoteBoot was a &lt;a href=&quot;https://www.kickstarter.com/projects/658903329/remoteboot-wifi-remote-management-module-for-pcs&quot;&gt;Kickstarter project&lt;/a&gt; which I ran in 2016, when I ran into the frustration of having to leave my computer on all day to be able to remotely access it, having no way of powering it on remotely. Thus, I came up with the idea of RemoteBoot, a module fitting into any desktop computer that allows for remote turn on and monitoring over WiFi.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/remoteboot.png&quot; alt=&quot;Picture of the RemoteBoot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This device consists of a WiFi module (ESP8266), transistors for turning the computer on and off, as well as optoisolators to enable LED monitoring. The firmware was written in C++, with a Javascript web UI, along with a PHP based cloud management interface.&lt;/p&gt;

&lt;p&gt;This Kickstarter project was extremely successful, raising AU$17,589 from 431 backers, with 1000 units manufactured in Shenzhen and successfully delivered to all backers. This project was also covered by some engineering youtubers, such as &lt;a href=&quot;https://youtu.be/lKhESCFeHy0&quot;&gt;Dave Jones from the EEVBlog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The over 800 devices in use today save significant amounts of energy worldwide, which to date have saved over 600MWh of energy, and reduced CO2 emissions by over 400 tons.&lt;/p&gt;</content><author><name></name></author><summary type="html">RemoteBoot was a Kickstarter project which I ran in 2016, when I ran into the frustration of having to leave my computer on all day to be able to remotely access it, having no way of powering it on remotely. Thus, I came up with the idea of RemoteBoot, a module fitting into any desktop computer that allows for remote turn on and monitoring over WiFi.</summary></entry><entry><title type="html">Perf+</title><link href="/2020/07/10/perf.html" rel="alternate" type="text/html" title="Perf+" /><published>2020-07-10T00:00:00+00:00</published><updated>2020-07-10T00:00:00+00:00</updated><id>/2020/07/10/perf</id><content type="html" xml:base="/2020/07/10/perf.html">&lt;p&gt;Perf+ was a series of crowdfunding projects which I ran in 2015 and 2017, which improved the way electronics prototyping was traditionally done.&lt;/p&gt;

&lt;p&gt;By combining a traditional perfboard (FR4 board with 0.1” grid of plated through holes) with sets of wires built into the board which are disconnected by default to the holes, but could be joined with a solder blob. There were horizontal wires on one side of the board, and vertical wires on the other, allowing any two points on the board to be connected easily without using any jumper wires.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/perf+.png&quot; alt=&quot;Picture of the RemoteBoot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The above is a simple routing example, the yellow dots represent the points to connect and the red is solder on each side to the points they are connecting.&lt;/p&gt;

&lt;p&gt;In 2015, I created a &lt;a href=&quot;https://www.kickstarter.com/projects/658903329/perf-the-perfboard-reinvented&quot;&gt;Kickstarter project&lt;/a&gt; for this concept, which successfully raised AU$15,461 from 614 backers. Over 8,000 boards were manufactured in Shenzhen, and all rewards were fulfilled before schedule.&lt;/p&gt;

&lt;p&gt;In 2016, with customer feedback from the first revision, I revised the design to improve the user experience of the boards, and created a &lt;a href=&quot;https://www.crowdsupply.com/ben-wang/perf-2&quot;&gt;project&lt;/a&gt; with CrowdSupply, another crowdfunding platform. On CrowdSupply, I raised $11,898 from 271 backers. This time, over 10,000 boards were manufactured, and all rewards were fulfilled on schedule.&lt;/p&gt;

&lt;p&gt;These boards were reviewed by many online publications, such as &lt;a href=&quot;https://hackaday.com/2016/06/16/evaluating-the-unusual-and-innovative-perf-protoboard/&quot;&gt;Hackaday&lt;/a&gt; and &lt;a href=&quot;https://youtu.be/oWqHp7PtzYA?t=1670&quot;&gt;EEVBlog&lt;/a&gt;, and are now available on &lt;a href=&quot;https://www.amazon.com/Perf-Prototyping-board-pack/dp/B06Y2CYYLZ&quot;&gt;Amazon&lt;/a&gt;.&lt;/p&gt;</content><author><name></name></author><summary type="html">Perf+ was a series of crowdfunding projects which I ran in 2015 and 2017, which improved the way electronics prototyping was traditionally done.</summary></entry><entry><title type="html">Welcome</title><link href="/2020/07/10/welcome.html" rel="alternate" type="text/html" title="Welcome" /><published>2020-07-10T00:00:00+00:00</published><updated>2020-07-10T00:00:00+00:00</updated><id>/2020/07/10/welcome</id><content type="html" xml:base="/2020/07/10/welcome.html">&lt;p&gt;My name is Ben Wang and I am an undergraduate student studying electrical engineering and management at the University of Pennsylvania.&lt;/p&gt;

&lt;p&gt;I have a wide range of interests, including power electronics, embedded devices, FPGAs, blockchains, computer vision, networking and AI (Phew that was a mouthfull). I hope to share my journey exploring these topics here.&lt;/p&gt;

&lt;p&gt;If you like the work shown here and are looking for someone in these fields, I would love to hear from you!&lt;/p&gt;</content><author><name></name></author><summary type="html">My name is Ben Wang and I am an undergraduate student studying electrical engineering and management at the University of Pennsylvania.</summary></entry></feed>