← All projects

Subscale wind tunnel — design, build, and CFD study

Independent project, 2026. Designed, built, and analyzed on my own.

I built a 3D printed wind tunnel with servo controlled angle of attack, then used CFD to test a Clark Y airfoil inside it and in free air. The result surprised me. My own tunnel’s walls were hiding the airfoil’s stall, cutting lift at low angles and squeezing the flow to inflate it at high angles, and I measured exactly how much.

My role

Everything. Sizing, CAD, printing, wiring, Python, CFD setup, and the MATLAB analysis.

Test section
5 × 5 in
Contraction
2.56 : 1
Airspeed
2.3 m/s
Reynolds no.
≈ 6,200
CFD cases
10
Free air CL,max
0.87
The finished wind tunnel on a workbench with the mist generator, settling chamber, contraction, acrylic test section with the airfoil inside, diffuser, and inline duct fan

Key takeaways

  • Built the whole rig myself. Every section 3D printed in PLA, a Raspberry Pi setting the angle of attack, and a custom mist system for flow visualization.
  • The walls were hiding the stall. In free air, lift peaks at CL ≈ 0.87 near 15° and then drops. In my tunnel it keeps climbing through 20°, because the stalled wake blocks so much of the test section that the air squeezing past the airfoil speeds up and adds lift.
  • The cause is model size. A 2 in chord fills 40% of a 5 in test section, about twice the usual limit.
  • I caught my own CFD mistake with a sanity check. A negative drag result exposed that the solver was integrating forces on the tunnel walls along with the airfoil.

Same airfoil, walls on vs. walls off

Pick an angle of attack. Both runs use the identical mesh and physics, and the only change is the tunnel walls. The lift plot is my MATLAB output from the CFD results at Re ≈ 6,200.

In my tunnel
CFD velocity field in the tunnel at 0 degrees
Free air, walls removed
CFD velocity field in free air at 0 degrees

Velocity magnitude, 0 to 4 m/s. Blue is slow, red is fast.

Lift coefficient vs. angle of attack, from my MATLAB script. The dashed XFOIL line is my XFLR5 benchmark.
MATLAB plot of lift coefficient versus angle of attack. CFD in the tunnel in blue rises from 0.08 at 0 degrees to 1.01 at 20 degrees with no stall. CFD in free air in orange rises to a peak of 0.87 at 15 degrees and drops to 0.81 at 20. The XFLR5 benchmark in yellow dashes, labeled XFOIL in the legend, follows the same shape but sits lower.
In tunnel CL0.081
Free air CL0.236
Wall effect ΔCL−0.155

Show the numbers
AoACL tunnelCL free airCL XFLR5ΔCLCD tunnelCD free airL/D tunnelL/D free air

Coefficients are averaged over the last 300 iterations of each run. Free air 10° was still creeping up at the end of the run, so treat it as a lower bound (about 0.84–0.86).

Engineering deep dive

What I did

  • Sized the tunnel for a desktop footprint and designed it in SolidWorks xDesign
  • Printed about 1.9 kg of PLA on a Bambu Lab P2S, with large sections split in half to fit the printer bed
  • Wired and programmed a Raspberry Pi in Python to set the angle of attack with a servo, with a saved calibration and a live LCD display of the current angle
  • Ran 10 CFD cases in SimScale and wrote the MATLAB script that turns them into lift and drag coefficients

The call I made

My in-tunnel lift curve kept rising past 20° with no stall. I didn’t accept it, and I didn’t switch to a turbulence model that would converge more nicely but give the wrong physics. I set up a controlled experiment instead, with the same airfoil, mesh, and solver and the walls removed. That isolated the walls as the cause.

Result

  • At 0–5° the walls cut lift by ΔCL ≈ 0.16, most likely because thick, slow boundary layers on the side walls weaken the flow near the airfoil
  • At 20° they inflate lift 25%, because the stalled wake blocks the test section and the squeezed flow around it speeds up
  • The effect flips sign near 13°, which masks the free air stall
  • Root cause is a 0.4 chord to height ratio, against typical guidance of about 0.2

Design and build

Labeled side view of the tunnel showing the settling chamber, 8 to 5 inch contraction, 5 by 5 inch test section with Clark Y airfoil on a 6 mm pitch shaft, diffuser, and fan mount
Layout and flow path. Flow runs left to right.
SolidWorks xDesign isometric of the tunnel with the airfoil and pitch shaft in the test section
SolidWorks xDesign assembly
Inlet packed with drinking straws as a flow straightener, with an orange three nozzle mist manifold pressed against it
Straw flow straightener and the three nozzle mist rake
CAD of the custom mist generator with an oval reservoir, vented lid, and three tapered nozzles
Custom mist generator, designed and printed

As built

Final all-printed build, 0.5 in walls. Lengths are approximate.

SectionCross sectionLength
Settling chamber8 × 8 in~7 in
Contraction8 → 5 in~4 in
Test section5 × 5 in7.5 in
Diffuser5 → 8 in~6 in
Fan345 CFM inline duct fan
Total~27.5 in

Design decisions

A low contraction ratio, on purpose

Textbook tunnels run 6–10 to 1. I took 2.56 to 1 to keep the part short and printable. The lesson I took from it is that speed comes from continuity, V = Q/A. The contraction doesn’t make speed. It makes the flow cleaner by letting the settling chamber run slow.

Suction, not blowing

The fan sits at the back and pulls air through the tunnel, so its swirl and turbulence stay downstream of the test section and the air reaching the airfoil is cleaner.

I should have printed the honeycomb

I used about 400 cut drinking straws as the flow straightener. Cutting and packing them took far more time than I expected, and a 3D printed honeycomb would have been faster and given a cleaner, more uniform result.

The test article

A Clark Y airfoil with a 2 in chord, a classic low speed section with a flat bottom. It spans wall to wall to approximate 2D flow.

Instrumentation

Angle of attack control

A Raspberry Pi 4, programmed in Python, runs the servo and the display. I type an angle and the airfoil moves there.

  • MG996R metal gear servo on a clamping coupler that drives a 6 mm pitch shaft through the test section wall into the airfoil
  • Calibration step that levels the chord and saves that zero, so it survives restarts
  • Live LCD readout of the commanded angle in a printed stand, so the angle shows in every photo and video
Angled view from the fan end showing the inline duct fan, the test section windows, and the LCD in its printed stand
The LCD sits in a printed stand in front of the test section. The inline fan at the back pulls air through.

Measuring the real airspeed

No pitot tube yet, so I filmed a smoke puff at 240 fps crossing a known 5 in of the test section at full fan power.

V = 0.127 m ÷ (13 / 240 s)≈ 2.3 m/s
Re = V c / ν = 2.3 × 0.0508 / 1.87×10−5≈ 6,200

Air properties at about 1,800 m elevation are ρ ≈ 0.97 kg/m³ and ν ≈ 1.87×10−5 m²/s.

≈7–10×

below my design Reynolds number of 40,000–60,000

  • A 345 CFM fan should give about 10 m/s through a 5 in section. I measured 2.3.
  • The likely losses are the straws, the fan curve under load, and leaks where filler cracked at the contraction joints.
  • The lesson is that Reynolds number comes from the fan, the losses, and the leaks, not the fan’s CFM rating. Measure it early.

Flow visualization

Smoke visualization in the test section at 0 degrees angle of attack, with the LCD reading the angle

Stills from the sweep, one per angle. The LCD in the foreground shows the commanded angle. Qualitative only.

The full sweep. The servo steps the airfoil from 0° to 20° while the LCD shows each angle.

What the smoke can and can’t tell me

The mist visibly deflects around the airfoil through the sweep, but it’s diffuse and lit from above. It shows the flow qualitatively and doesn’t validate the CFD.

Next version
  • A small fan on the mist box lid to push the fog in at about inlet speed
  • A light sheet through a mid-span slit, with the camera square to the window on a tripod

CFD method

Setup

SolverSimScale, incompressible, steady, laminar
Inlet and outlet2.3 m/s velocity inlet, 0 Pa gauge outlet
In tunnel5 × 5 in channel, all four walls no-slip
Free air±10 chords tall with slip side faces. Chord to height drops from 0.40 to 0.05
Mesh~3.5M cells. 5 inflation layers, 0.05 mm first layer, ~100 cells per surface along the chord
Iterations1,000 per run, 2,000 for the slower free air cases
Angles0, 5, 10, 15, 20° in both domains

Why laminar

At Re ≈ 6,200 the boundary layer stays laminar. Transition on an airfoil typically happens around 105, and XFLR5 predicted none anywhere on the surface. A RANS model like k-ω SST would add artificial eddy viscosity, delay separation, and over-predict lift near stall. Laminar was harder to converge, so I kept it and documented the limits.

Mistakes I caught

  1. Negative dragFixed

    First results showed −0.006 N of drag and 4× too much skin friction. The force calculation was integrating all six tunnel faces along with the airfoil. I rebuilt every run with forces on the airfoil only.

  2. Cutting runs shortAvoided

    Drag settled by iteration 200 but lift drifted until 700. Cutting runs to 400 would have under-predicted lift by about 4%.

MATLAB plot of lift and drag coefficient versus iteration at 0 degrees in the tunnel. Drag is flat by about iteration 200, lift settles only after about 700.
Convergence at 0°. Drag settles by 200 iterations and lift not until about 700. The early spikes are the solver correcting its initial guess.
SimScale residual plot at 20 degrees in the tunnel, plateauing and oscillating instead of dropping
Residuals at 20° plateau and oscillate, the signature of unsteady separated flow a steady solver can’t settle. I report the time average with min–max bands.

Results

Five by four grid of CFD images. Rows are 0 to 20 degrees, columns are velocity in tunnel, velocity in free air, pressure in tunnel, pressure in free air. At 20 degrees the in-tunnel wake is pinned between the walls with fast flow squeezed above and below.
The key image. From 10° the flow separates from the upper surface. At 20° the walls pin the wake and squeeze fast flow around it, which is the blockage that inflates lift. In free air the wake spreads with nothing to push against. In-tunnel pressure looks lighter overall because wall friction lifts the whole field by 0.3–0.5 Pa, so compare shapes, not shades.
Bar chart of wall effect on lift, minus 0.155 at 0 degrees, minus 0.161 at 5, minus 0.074 at 10, plus 0.034 at 15, plus 0.202 at 20
The wall effect flips sign. Slow side wall boundary layers most likely dominate at low angles and cut lift. Blockage dominates at high angles, where squeezed flow speeds up and adds lift.
L over D versus angle of attack. Free air peaks at 8.0 at 5 degrees, tunnel peaks at 5.5 at 10 degrees.
The walls cost about 30% of peak L/D. They cut lift at the low angles where the airfoil is most efficient, which moves the best angle from 5° to 10°.
Drag coefficient versus angle of attack for tunnel, free air, and the XFLR5 benchmark
Blockage adds drag from 5° up, 0.354 vs. 0.284 at 20°, because the faster squeezed flow loads the airfoil harder.

Why the walls change lift

Low angles, less lift. At this low Reynolds number the boundary layers on the no-slip side walls are thick and slow, and they meet the airfoil at both ends. That most likely weakens the flow over the airfoil. In free air those faces are slip, and the loss disappears.

High angles, more lift. At 20° the airfoil fills about 18% of the test section height, and its stalled wake fills more. The air has to squeeze through the gaps, so it speeds up. Force scales with velocity squared, so lift goes up and the stall disappears.

What it means

Low Reynolds number is a different world

The boundary layer stays fully laminar and separation starts near 10°. Camber barely helps at 0° (CL 0.24 in free air vs. about 0.35 for a Clark Y at high Re), the stall is soft, and best L/D is about 8 instead of 50+.

A test rig can lie to you

Without the free air runs, I would have reported an airfoil that doesn’t stall by 20°. The walls changed the answer, so the model has to be sized to the tunnel, or the results need wall corrections.

CFD vs. XFLR5

Same trends, with near zero lift at 0°, a reduced lift slope, and a soft stall. CFD predicts more lift even in free air, which fits the known weakness of XFLR5’s solver below Re ≈ 104.

Limits, lessons, and what’s next

Limits I’m upfront about

The tunnel has no force balance or pressure taps, so every lift and drag number here is CFD. The steady solver time-averages unsteady flow at 10–20°, and my transient run at 15° didn’t complete. One mesh, no grid independence study. The 5° spacing brackets the free air stall at about 12–15°. Airspeed comes from one method, smoke timing.

Lessons

Size the model to the tunnel, with a chord to height ratio of 0.2 or less. Measure the real speed early and seal every joint. Sanity check CFD against physics and geometry before trusting it. Judge convergence on the number you care about, not just residuals. Pick the correct physics even when it converges worse.

Next

A 1 in chord or a taller test section to cut the interference. A printed honeycomb in place of the straws. A pitot-static tube with a differential pressure sensor on the Pi for live airspeed. A load cell force balance to check the CFD directly. Transient CFD and a mesh study near stall, with finer angle spacing from 12° to 18°.

Let’s build the next one.

Boulder, CO. Available now and open to relocation.