123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- // This code simulates the harmonic mechanical deflection of a 3D PMUT while supposing axisymmetry.
- // A PMUT is a microscale ultrasonic transducer with a piezoelectric actuation. In this example
- // the PMUT vibrates in air but it is straightforward to use another medium (e.g. water).
- //
- // The PMUT geometry and mesh in this example are created using sparselizard but GMSH could have been used instead.
- // In any case the goal is not to illustrate the geometry and mesh creation but rather the PMUT axisymmetric simulation.
- // The PMUT is made up of (from bottom layer to top):
- //
- // - a substrate (not in this example)
- // - a vacuum cavity
- // - a membrane layer
- // - a bottom metal layer
- // - a piezo layer (sandwiched between the two metal layers)
- // - a top metal layer
- // - a fluid
- //
- // Refer to the following paper for more details:
- //
- // "Characterization of polymer-based piezoelectric micromachined ultrasound transducers for short-range gesture
- // recognition applications", P. Gijsenbergh, A. Halbach et al.
- //
- // Please note that since this PMUT is being fabricated the materials have been modified for confidentiality reasons.
- // Standard literature properties or arbitrary values have been considered for the material properties.
- // It should thus be no surprise that the resonance frequencies and deflections do not match the paper values.
- #include "sparselizardbase.h"
- using namespace mathop;
- // Arguments are:
- //
- // PMUT radius, radius of the fluid region around (should be large enough), thickness of the top metal, piezo layer and bottom metal,
- // thickness of the membrane, thickness of the cavity, electrode coverage (in %), x-length of the pillar under the membrane and wavelength in the fluid.
- //
- mesh createmesh(double r, double rfluid, double thtopelec, double thpiezo, double thbotelec, double thmem, double thcav, double cov, double lpillar, double wavelength);
- void sparselizard(void)
- {
- wallclock clk;
-
- // Driving frequency [Hz]:
- double f0 = 165.31e3;
-
- // Acoustic propagation speed c [m/s] and a scaling factor for numerical conditionning:
- double c = 340, scaling = 1e10;
- // Define the PMUT geometric dimensions [m]:
- double r = 300e-6, rfluid = 20e-3, thtopelec = 100e-9, thpiezo = 500e-9, thbotelec = 100e-9, thmem = 15e-6, thcav = 35e-6, cov = 0.67, lpillar = 300e-6;
- // Axisymmetric assumption:
- setaxisymmetry();
- // The domain regions as defined in 'createmesh':
- int piezo = 1, membrane = 2, topelec = 3, botelec = 4, pillar = 5, fluid = 6, clamp = 7, electrode = 8, fluidboundary = 9;
- // Create the geometry and the mesh:
- mesh mymesh = createmesh(r, rfluid, thtopelec, thpiezo, thbotelec, thmem, thcav, cov, lpillar, c/f0);
- // Write the mesh for display:
- mymesh.write("pmutaxisym.msh");
- // Define additional regions:
- int ground = regionintersection({piezo, botelec});
- int solid = regionunion({pillar, membrane, botelec, piezo, topelec});
- int isotropicsolid = regionunion({pillar, membrane, botelec, topelec});
- int pmuttop = regionintersection({topelec,fluid});
- // Harmonic analysis. Set the fundamental frequency [Hz]:
- setfundamentalfrequency(f0);
- // Nodal shape functions 'h1' for v (the electric potential), p (acoustic
- // pressure) and u (membrane displacement). Three components are used for u.
- // Use harmonic 2 and 3, i.e. u(x,t) = Us(x)*sin(2pi*f0*t) + Uc(x)*cos(2pi*f0*t)
- // for u and p and harmonic 2, i.e. v(x,t) = V(x)*sin(2pi*f0*t) for v.
- // The y coordinate field is also defined since it might be of interest.
- //
- field u("h1xyz",{2,3}), p("h1", {2,3}), v("h1",{2}), y("y");
- // Use interpolation order 2 for p and u and 1 for v:
- u.setorder(solid, 2);
- p.setorder(fluid, 2);
- v.setorder(piezo, 1);
- // Clamp on the clamp region:
- u.setconstraint(clamp);
- v.harmonic(2).setconstraint(electrode, 1);
- v.setconstraint(ground, 0);
- // Young's modulus [Pa], Poisson's ratio [] (for isotropic materials) and the density [kg/m^3]:
- parameter E, nu, rho;
- E|pillar = 3.1e9;
- E|membrane = 3.1e9;
- E|botelec = 100e9;
- E|topelec = 100e9;
- nu|pillar = 0.34;
- nu|membrane = 0.34;
- nu|botelec = 0.3;
- nu|topelec = 0.3;
- rho|pillar = 1300;
- rho|membrane = 1300;
- rho|botelec = 1000;
- rho|piezo = 1780;
- rho|topelec = 1000;
- rho|fluid = 1.2;
-
- // Acoustic attenuation alpha [Neper/m] at the considered frequency:
- expression alpha = dbtoneper(5.4);
-
- // Diagonal relative permittivity matrix for the piezo:
- expression K(3,3,{7.4,7.4,8.0});
- K = K * 8.854e-12;
- // Coupling matrix [C/m^2] in Voigt notation for the piezo (6 rows, 3 columns).
- // The matrix is expressed in the usual coordinates, i.e. with z being the layer growth direction.
- expression C(6,3,{0,0,0.024, 0,0,0.024, 0,0,-0.027, 0,-0.015,0, -0.015,0,0, 0,0,0});
- // Elasticity matrix [Pa] in Voigt notation for the piezo.
- // The matrix is expressed in the usual coordinates, i.e. with z being the layer growth direction.
- expression H(6,6, {3.8e9, 1.9e9,3.8e9, 0.9e9,0.9e9,1.2e9, 0,0,0,7e8, 0,0,0,0,7e8, 0,0,0,0,0,9e8});
- // Take into account that here y is the growth direction and not z.
- // Refer to the documentation to make sure you understand how to use 'rotate'!
- K.rotate(-90,0,0); C.rotate(-90,0,0,"K","RT"); H.rotate(-90,0,0,"K","KT");
-
- formulation pmutmodel;
- // Standard isotropic elasticity. dof(u) is the unknown field, tf(u) the test function field.
- pmutmodel += integral(isotropicsolid, predefinedelasticity(dof(u), tf(u), E, nu) );
- // All inertia terms:
- pmutmodel += integral(solid, -rho*dtdt(dof(u))*tf(u) );
- // The classical weak formulation for piezoelectricity below can be found e.g. in paper:
- //
- // "A new fnite-element formulation for electromechanical boundary value problems"
- // Define the mechanical equations of the problem in the piezo.
- // strain(u) returns the strain vector [exx,eyy,ezz,gyz,gxz,gxy] of u.
- pmutmodel += integral(piezo, -( H*strain(dof(u)) )*strain(tf(u)) -( C*grad(dof(v)) )*strain(tf(u)) );
- // Define the electrical equations of the problem in the piezo:
- pmutmodel += integral(piezo, ( transpose(C)*strain(dof(u)) )*grad(tf(v)) - ( K*grad(dof(v)) )*grad(tf(v)) );
- // The wave equation is solved in the fluid:
- pmutmodel += integral(fluid, predefinedacoustics(dof(p), tf(p), c, alpha));
- // A Sommerfeld condition is used on the fluid boundary to have outgoing waves:
- pmutmodel += integral(fluidboundary, predefinedacousticradiation(dof(p), tf(p), c, alpha));
- // Elastoacoustic coupling terms are predefined. They consist in two terms.
- // The first term is the pressure applied by the fluid normal to the PMUT top.
- // The second term comes from Newton's law: a membrane acceleration creates a
- // pressure gradient in the fluid.
- //
- // To have a good matrix conditionning the pressure source is divided by
- // the scaling factor and to compensate it multiplies the pressure force.
- // This leads to the correct membrane deflection but a pressure field divided by the scaling factor.
- pmutmodel += integral(pmuttop, predefinedacousticstructureinteraction(dof(p), tf(p), dof(u), tf(u), c, rho, array3x1(0,1,0), alpha, scaling));
- // Generate, solve and transfer the solution to the u, p and v fields:
- solve(pmutmodel);
- // Write the deflection, pressure and electric potential to file (with an order 2 interpolation for p and u).
- u.write(solid, "u.vtk", 2);
- (scaling*p).write(fluid, "p.vtk", 2);
- v.write(piezo, "v.vtk", 1);
- // Write p with 50 timesteps for illustration:
- // (scaling*p).write(fluid, "p.vtk", 2, 50);
- // Output the peak deflection:
- std::cout << "Peak in-phase deflection: " << 1e9*abs(u.compy().harmonic(2)).max(solid, 6)[0] << " nm" << std::endl;
- std::cout << "Peak quadrature deflection: " << 1e9*abs(u.compy().harmonic(3)).max(solid, 6)[0] << " nm" << std::endl;
- // Output the pressure at 'rfluid' meters above the PMUT center:
- double pressureabove = sqrt( pow(scaling*p.harmonic(2),2) + pow(scaling*p.harmonic(3),2) ).interpolate(fluid, {0,rfluid,0})[0];
- double peakpressure = sqrt( pow(scaling*p.harmonic(2),2) + pow(scaling*p.harmonic(3),2) ).max(fluid, 5)[0];
- std::cout << "Pressure at " << 1000*rfluid << " mm above PMUT center: " << pressureabove << " Pa" << std::endl;
- std::cout << "Peak pressure is " << peakpressure << " Pa" << std::endl;
- clk.print("Total computation time:");
- // Code validation line. Can be removed.
- std::cout << (pressureabove < 0.278699 && pressureabove > 0.278697);
- }
- // THE MESH BELOW IS FULLY STRUCTURED AND IS CREATED USING THE (BASIC) SPARSELIZARD GEOMETRY CREATION TOOL.
- // THE ADVANTAGE OF IT IS THAT THE CODE ABOVE CAN BE CALLED FOR ANY PMUT DIMENSION WITHOUT NEEDING CALLS TO EXTERNAL MESHING SOFTWARE.
- // AS AN ALTERNATIVE, GMSH COULD HAVE BEEN USED TO EASILY DEFINE THE GEOMETRY AND CREATE A DELAUNAY MESH IN THE FLUID.
- mesh createmesh(double r, double rfluid, double thtopelec, double thpiezo, double thbotelec, double thmem, double thcav, double cov, double lpillar, double wavelength)
- {
- // Give names to the physical region numbers:
- int piezo = 1, membrane = 2, topelec = 3, botelec = 4, pillar = 5, fluid = 6, clamp = 7, electrode = 8, fluidboundary = 9;
- // Number of mesh layers:
- int nxpmut = 10, nzthick = 10, nzthin = 5;
- int nxpillar = nxpmut*lpillar/r;
-
- // Calculate the number of fluid elements to have about 10 elements per wavelength:
- int nair = std::ceil(10.0 * rfluid/wavelength);
-
- // Cavity layer:
- double h = -(thcav+thmem+thbotelec+thpiezo+thtopelec);
- shape q13("quadrangle", pillar, {r,h,0, r+lpillar,h,0, r+lpillar,h+thcav,0, r,h+thcav,0}, {nxpillar, nzthick, nxpillar, nzthick});
- shape clampline1 = q13.getsons()[0];
- clampline1.setphysicalregion(clamp);
- shape clampline2 = q13.getsons()[1];
- clampline2.setphysicalregion(clamp);
- // Membrane layer:
- h = h+thcav;
- shape q21("quadrangle", membrane, {0,h,0, r*cov,h,0, r*cov,h+thmem,0, 0,h+thmem,0}, {int(nxpmut*cov), nzthick, int(nxpmut*cov), nzthick});
- shape q22("quadrangle", membrane, {r*cov,h,0, r,h,0, r,h+thmem,0, r*cov,h+thmem,0}, {int(nxpmut*(1-cov)), nzthick, int(nxpmut*(1-cov)), nzthick});
- shape q23("quadrangle", membrane, {r,h,0, r+lpillar,h,0, r+lpillar,h+thmem,0, r,h+thmem,0}, {nxpillar, nzthick, nxpillar, nzthick});
- // Bottom electrode layer:
- h = h+thmem;
- shape q31("quadrangle", botelec, {0,h,0, r*cov,h,0, r*cov,h+thbotelec,0, 0,h+thbotelec,0}, {int(nxpmut*cov), nzthin, int(nxpmut*cov), nzthin});
- shape q32("quadrangle", botelec, {r*cov,h,0, r,h,0, r,h+thbotelec,0, r*cov,h+thbotelec,0}, {int(nxpmut*(1-cov)), nzthin, int(nxpmut*(1-cov)), nzthin});
- shape q33("quadrangle", botelec, {r,h,0, r+lpillar,h,0, r+lpillar,h+thbotelec,0, r,h+thbotelec,0}, {nxpillar, nzthin, nxpillar, nzthin});
- // Piezo layer:
- h = h+thbotelec;
- shape q41("quadrangle", piezo, {0,h,0, r*cov,h,0, r*cov,h+thpiezo,0, 0,h+thpiezo,0}, {int(nxpmut*cov), nzthin, int(nxpmut*cov), nzthin});
- shape q42("quadrangle", piezo, {r*cov,h,0, r,h,0, r,h+thpiezo,0, r*cov,h+thpiezo,0}, {int(nxpmut*(1-cov)), nzthin, int(nxpmut*(1-cov)), nzthin});
- shape q43("quadrangle", piezo, {r,h,0, r+lpillar,h,0, r+lpillar,h+thpiezo,0, r,h+thpiezo,0}, {nxpillar, nzthin, nxpillar, nzthin});
- // Top electrode layer:
- h = h+thpiezo;
- shape q51("quadrangle", topelec, {0,h,0, r*cov,h,0, r*cov,h+thtopelec,0, 0,h+thtopelec,0}, {int(nxpmut*cov), nzthin, int(nxpmut*cov), nzthin});
- shape q52("quadrangle", topelec, {r*cov,h,0, r,h,0, r,h+thtopelec,0, r*cov,h+thtopelec,0}, {int(nxpmut*(1-cov)), nzthin, int(nxpmut*(1-cov)), nzthin});
- shape q53("quadrangle", topelec, {r,h,0, r+lpillar,h,0, r+lpillar,h+thtopelec,0, r,h+thtopelec,0}, {nxpillar, nzthin, nxpillar, nzthin});
- shape electrodeline = q51.getsons()[0];
- electrodeline.setphysicalregion(electrode);
- // Fluid:
- shape q61("quadrangle", fluid, {0,0,0, r*cov,0,0, r*cov,r+lpillar,0, 0,r+lpillar,0}, {int(nxpmut*cov), int(nxpmut*(r+lpillar)/r), int(nxpmut*cov), int(nxpmut*(r+lpillar)/r)});
- shape q62("quadrangle", fluid, {r*cov,0,0, r,0,0, r,r+lpillar,0, r*cov,r+lpillar,0}, {int(nxpmut*(1-cov)), int(nxpmut*(r+lpillar)/r), int(nxpmut*(1-cov)), int(nxpmut*(r+lpillar)/r)});
- shape q63("quadrangle", fluid, {r,0,0, r+lpillar,0,0, r+lpillar,r+lpillar,0, r,r+lpillar,0}, {nxpillar, int(nxpmut*(r+lpillar)/r), nxpillar, int(nxpmut*(r+lpillar)/r)});
- shape l1 = q61.getsons()[2];
- shape l2("line", -1, {r*cov,r+lpillar,0, r*cov,sqrt(pow(rfluid,2)-pow(r*cov,2)),0}, nair);
- shape l3("arc", -1, {r*cov,sqrt(pow(rfluid,2)-pow(r*cov,2)),0, 0,rfluid,0, 0,0,0}, int(nxpmut*cov));
- shape l4("line", -1, {0,r+lpillar,0, 0,rfluid,0}, nair);
- shape q71("quadrangle", fluid, {l1,l2,l3,l4});
- shape l5 = q62.getsons()[2];
- shape l6("line", -1, {r,r+lpillar,0, r,sqrt(pow(rfluid,2)-pow(r,2)),0}, nair);
- shape l7("arc", -1, {r,sqrt(pow(rfluid,2)-pow(r,2)),0, r*cov,sqrt(pow(rfluid,2)-pow(r*cov,2)),0, 0,0,0}, int(nxpmut*(1-cov)));
- shape q72("quadrangle", fluid, {l5,l6,l7,l2});
- shape l8 = q63.getsons()[2];
- shape l9("line", -1, {r+lpillar,r+lpillar,0, rfluid*sqrt(2)/2,rfluid*sqrt(2)/2,0}, nair);
- shape l10("arc", -1, {rfluid*sqrt(2)/2,rfluid*sqrt(2)/2,0, r,sqrt(pow(rfluid,2)-pow(r,2)),0, 0,0,0}, nxpillar);
- shape q73("quadrangle", fluid, {l8,l9,l10,l6});
- shape l11("line", -1, {r+lpillar,0,0, rfluid,0,0}, nair);
- shape l12("arc", -1, {rfluid,0,0, rfluid*sqrt(2)/2,rfluid*sqrt(2)/2,0, 0,0,0}, int(nxpmut*(r+lpillar)/r));
- shape l13 = q63.getsons()[1];
- shape q74("quadrangle", fluid, {l11,l12,l9,l13});
- // Fluid boundary:
- shape fb1 = q71.getsons()[2];
- fb1.setphysicalregion(fluidboundary);
- shape fb2 = q72.getsons()[2];
- fb2.setphysicalregion(fluidboundary);
- shape fb3 = q73.getsons()[2];
- fb3.setphysicalregion(fluidboundary);
- shape fb4 = q74.getsons()[1];
- fb4.setphysicalregion(fluidboundary);
- // Provide to the mesh all shapes of interest:
- mesh mymesh({q13,q21,q22,q23,q31,q32,q33,q41,q42,q43,q51,q52,q53,q61,q62,q63,q71,q72,q73,q74, clampline1,clampline2, electrodeline, fb1,fb2,fb3,fb4});
- return mymesh;
- }
- int main(void)
- {
- SlepcInitialize(0,{},0,0);
- sparselizard();
- SlepcFinalize();
- return 0;
- }
|