// 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; }