main.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. // This code simulates the harmonic mechanical deflection of a 3D PMUT while supposing axisymmetry.
  2. // A PMUT is a microscale ultrasonic transducer with a piezoelectric actuation. In this example
  3. // the PMUT vibrates in air but it is straightforward to use another medium (e.g. water).
  4. //
  5. // The PMUT geometry and mesh in this example are created using sparselizard but GMSH could have been used instead.
  6. // In any case the goal is not to illustrate the geometry and mesh creation but rather the PMUT axisymmetric simulation.
  7. // The PMUT is made up of (from bottom layer to top):
  8. //
  9. // - a substrate (not in this example)
  10. // - a vacuum cavity
  11. // - a membrane layer
  12. // - a bottom metal layer
  13. // - a piezo layer (sandwiched between the two metal layers)
  14. // - a top metal layer
  15. // - a fluid
  16. //
  17. // Refer to the following paper for more details:
  18. //
  19. // "Characterization of polymer-based piezoelectric micromachined ultrasound transducers for short-range gesture
  20. // recognition applications", P. Gijsenbergh, A. Halbach et al.
  21. //
  22. // Please note that since this PMUT is being fabricated the materials have been modified for confidentiality reasons.
  23. // Standard literature properties or arbitrary values have been considered for the material properties.
  24. // It should thus be no surprise that the resonance frequencies and deflections do not match the paper values.
  25. #include "sparselizardbase.h"
  26. using namespace mathop;
  27. // Arguments are:
  28. //
  29. // PMUT radius, radius of the fluid region around (should be large enough), thickness of the top metal, piezo layer and bottom metal,
  30. // thickness of the membrane, thickness of the cavity, electrode coverage (in %), x-length of the pillar under the membrane and wavelength in the fluid.
  31. //
  32. mesh createmesh(double r, double rfluid, double thtopelec, double thpiezo, double thbotelec, double thmem, double thcav, double cov, double lpillar, double wavelength);
  33. void sparselizard(void)
  34. {
  35. wallclock clk;
  36. // Driving frequency [Hz]:
  37. double f0 = 165.31e3;
  38. // Acoustic propagation speed c [m/s] and a scaling factor for numerical conditionning:
  39. double c = 340, scaling = 1e10;
  40. // Define the PMUT geometric dimensions [m]:
  41. 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;
  42. // Axisymmetric assumption:
  43. setaxisymmetry();
  44. // The domain regions as defined in 'createmesh':
  45. int piezo = 1, membrane = 2, topelec = 3, botelec = 4, pillar = 5, fluid = 6, clamp = 7, electrode = 8, fluidboundary = 9;
  46. // Create the geometry and the mesh:
  47. mesh mymesh = createmesh(r, rfluid, thtopelec, thpiezo, thbotelec, thmem, thcav, cov, lpillar, c/f0);
  48. // Write the mesh for display:
  49. mymesh.write("pmutaxisym.msh");
  50. // Define additional regions:
  51. int ground = regionintersection({piezo, botelec});
  52. int solid = regionunion({pillar, membrane, botelec, piezo, topelec});
  53. int isotropicsolid = regionunion({pillar, membrane, botelec, topelec});
  54. int pmuttop = regionintersection({topelec,fluid});
  55. // Harmonic analysis. Set the fundamental frequency [Hz]:
  56. setfundamentalfrequency(f0);
  57. // Nodal shape functions 'h1' for v (the electric potential), p (acoustic
  58. // pressure) and u (membrane displacement). Three components are used for u.
  59. // Use harmonic 2 and 3, i.e. u(x,t) = Us(x)*sin(2pi*f0*t) + Uc(x)*cos(2pi*f0*t)
  60. // for u and p and harmonic 2, i.e. v(x,t) = V(x)*sin(2pi*f0*t) for v.
  61. // The y coordinate field is also defined since it might be of interest.
  62. //
  63. field u("h1xyz",{2,3}), p("h1", {2,3}), v("h1",{2}), y("y");
  64. // Use interpolation order 2 for p and u and 1 for v:
  65. u.setorder(solid, 2);
  66. p.setorder(fluid, 2);
  67. v.setorder(piezo, 1);
  68. // Clamp on the clamp region:
  69. u.setconstraint(clamp);
  70. v.harmonic(2).setconstraint(electrode, 1);
  71. v.setconstraint(ground, 0);
  72. // Young's modulus [Pa], Poisson's ratio [] (for isotropic materials) and the density [kg/m^3]:
  73. parameter E, nu, rho;
  74. E|pillar = 3.1e9;
  75. E|membrane = 3.1e9;
  76. E|botelec = 100e9;
  77. E|topelec = 100e9;
  78. nu|pillar = 0.34;
  79. nu|membrane = 0.34;
  80. nu|botelec = 0.3;
  81. nu|topelec = 0.3;
  82. rho|pillar = 1300;
  83. rho|membrane = 1300;
  84. rho|botelec = 1000;
  85. rho|piezo = 1780;
  86. rho|topelec = 1000;
  87. rho|fluid = 1.2;
  88. // Acoustic attenuation alpha [Neper/m] at the considered frequency:
  89. expression alpha = dbtoneper(5.4);
  90. // Diagonal relative permittivity matrix for the piezo:
  91. expression K(3,3,{7.4,7.4,8.0});
  92. K = K * 8.854e-12;
  93. // Coupling matrix [C/m^2] in Voigt notation for the piezo (6 rows, 3 columns).
  94. // The matrix is expressed in the usual coordinates, i.e. with z being the layer growth direction.
  95. 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});
  96. // Elasticity matrix [Pa] in Voigt notation for the piezo.
  97. // The matrix is expressed in the usual coordinates, i.e. with z being the layer growth direction.
  98. 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});
  99. // Take into account that here y is the growth direction and not z.
  100. // Refer to the documentation to make sure you understand how to use 'rotate'!
  101. K.rotate(-90,0,0); C.rotate(-90,0,0,"K","RT"); H.rotate(-90,0,0,"K","KT");
  102. formulation pmutmodel;
  103. // Standard isotropic elasticity. dof(u) is the unknown field, tf(u) the test function field.
  104. pmutmodel += integral(isotropicsolid, predefinedelasticity(dof(u), tf(u), E, nu) );
  105. // All inertia terms:
  106. pmutmodel += integral(solid, -rho*dtdt(dof(u))*tf(u) );
  107. // The classical weak formulation for piezoelectricity below can be found e.g. in paper:
  108. //
  109. // "A new fnite-element formulation for electromechanical boundary value problems"
  110. // Define the mechanical equations of the problem in the piezo.
  111. // strain(u) returns the strain vector [exx,eyy,ezz,gyz,gxz,gxy] of u.
  112. pmutmodel += integral(piezo, -( H*strain(dof(u)) )*strain(tf(u)) -( C*grad(dof(v)) )*strain(tf(u)) );
  113. // Define the electrical equations of the problem in the piezo:
  114. pmutmodel += integral(piezo, ( transpose(C)*strain(dof(u)) )*grad(tf(v)) - ( K*grad(dof(v)) )*grad(tf(v)) );
  115. // The wave equation is solved in the fluid:
  116. pmutmodel += integral(fluid, predefinedacoustics(dof(p), tf(p), c, alpha));
  117. // A Sommerfeld condition is used on the fluid boundary to have outgoing waves:
  118. pmutmodel += integral(fluidboundary, predefinedacousticradiation(dof(p), tf(p), c, alpha));
  119. // Elastoacoustic coupling terms are predefined. They consist in two terms.
  120. // The first term is the pressure applied by the fluid normal to the PMUT top.
  121. // The second term comes from Newton's law: a membrane acceleration creates a
  122. // pressure gradient in the fluid.
  123. //
  124. // To have a good matrix conditionning the pressure source is divided by
  125. // the scaling factor and to compensate it multiplies the pressure force.
  126. // This leads to the correct membrane deflection but a pressure field divided by the scaling factor.
  127. pmutmodel += integral(pmuttop, predefinedacousticstructureinteraction(dof(p), tf(p), dof(u), tf(u), c, rho, array3x1(0,1,0), alpha, scaling));
  128. // Generate, solve and transfer the solution to the u, p and v fields:
  129. solve(pmutmodel);
  130. // Write the deflection, pressure and electric potential to file (with an order 2 interpolation for p and u).
  131. u.write(solid, "u.vtk", 2);
  132. (scaling*p).write(fluid, "p.vtk", 2);
  133. v.write(piezo, "v.vtk", 1);
  134. // Write p with 50 timesteps for illustration:
  135. // (scaling*p).write(fluid, "p.vtk", 2, 50);
  136. // Output the peak deflection:
  137. std::cout << "Peak in-phase deflection: " << 1e9*abs(u.compy().harmonic(2)).max(solid, 6)[0] << " nm" << std::endl;
  138. std::cout << "Peak quadrature deflection: " << 1e9*abs(u.compy().harmonic(3)).max(solid, 6)[0] << " nm" << std::endl;
  139. // Output the pressure at 'rfluid' meters above the PMUT center:
  140. double pressureabove = sqrt( pow(scaling*p.harmonic(2),2) + pow(scaling*p.harmonic(3),2) ).interpolate(fluid, {0,rfluid,0})[0];
  141. double peakpressure = sqrt( pow(scaling*p.harmonic(2),2) + pow(scaling*p.harmonic(3),2) ).max(fluid, 5)[0];
  142. std::cout << "Pressure at " << 1000*rfluid << " mm above PMUT center: " << pressureabove << " Pa" << std::endl;
  143. std::cout << "Peak pressure is " << peakpressure << " Pa" << std::endl;
  144. clk.print("Total computation time:");
  145. // Code validation line. Can be removed.
  146. std::cout << (pressureabove < 0.278699 && pressureabove > 0.278697);
  147. }
  148. // THE MESH BELOW IS FULLY STRUCTURED AND IS CREATED USING THE (BASIC) SPARSELIZARD GEOMETRY CREATION TOOL.
  149. // THE ADVANTAGE OF IT IS THAT THE CODE ABOVE CAN BE CALLED FOR ANY PMUT DIMENSION WITHOUT NEEDING CALLS TO EXTERNAL MESHING SOFTWARE.
  150. // AS AN ALTERNATIVE, GMSH COULD HAVE BEEN USED TO EASILY DEFINE THE GEOMETRY AND CREATE A DELAUNAY MESH IN THE FLUID.
  151. mesh createmesh(double r, double rfluid, double thtopelec, double thpiezo, double thbotelec, double thmem, double thcav, double cov, double lpillar, double wavelength)
  152. {
  153. // Give names to the physical region numbers:
  154. int piezo = 1, membrane = 2, topelec = 3, botelec = 4, pillar = 5, fluid = 6, clamp = 7, electrode = 8, fluidboundary = 9;
  155. // Number of mesh layers:
  156. int nxpmut = 10, nzthick = 10, nzthin = 5;
  157. int nxpillar = nxpmut*lpillar/r;
  158. // Calculate the number of fluid elements to have about 10 elements per wavelength:
  159. int nair = std::ceil(10.0 * rfluid/wavelength);
  160. // Cavity layer:
  161. double h = -(thcav+thmem+thbotelec+thpiezo+thtopelec);
  162. shape q13("quadrangle", pillar, {r,h,0, r+lpillar,h,0, r+lpillar,h+thcav,0, r,h+thcav,0}, {nxpillar, nzthick, nxpillar, nzthick});
  163. shape clampline1 = q13.getsons()[0];
  164. clampline1.setphysicalregion(clamp);
  165. shape clampline2 = q13.getsons()[1];
  166. clampline2.setphysicalregion(clamp);
  167. // Membrane layer:
  168. h = h+thcav;
  169. 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});
  170. 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});
  171. shape q23("quadrangle", membrane, {r,h,0, r+lpillar,h,0, r+lpillar,h+thmem,0, r,h+thmem,0}, {nxpillar, nzthick, nxpillar, nzthick});
  172. // Bottom electrode layer:
  173. h = h+thmem;
  174. 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});
  175. 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});
  176. shape q33("quadrangle", botelec, {r,h,0, r+lpillar,h,0, r+lpillar,h+thbotelec,0, r,h+thbotelec,0}, {nxpillar, nzthin, nxpillar, nzthin});
  177. // Piezo layer:
  178. h = h+thbotelec;
  179. 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});
  180. 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});
  181. shape q43("quadrangle", piezo, {r,h,0, r+lpillar,h,0, r+lpillar,h+thpiezo,0, r,h+thpiezo,0}, {nxpillar, nzthin, nxpillar, nzthin});
  182. // Top electrode layer:
  183. h = h+thpiezo;
  184. 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});
  185. 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});
  186. shape q53("quadrangle", topelec, {r,h,0, r+lpillar,h,0, r+lpillar,h+thtopelec,0, r,h+thtopelec,0}, {nxpillar, nzthin, nxpillar, nzthin});
  187. shape electrodeline = q51.getsons()[0];
  188. electrodeline.setphysicalregion(electrode);
  189. // Fluid:
  190. 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)});
  191. 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)});
  192. 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)});
  193. shape l1 = q61.getsons()[2];
  194. shape l2("line", -1, {r*cov,r+lpillar,0, r*cov,sqrt(pow(rfluid,2)-pow(r*cov,2)),0}, nair);
  195. shape l3("arc", -1, {r*cov,sqrt(pow(rfluid,2)-pow(r*cov,2)),0, 0,rfluid,0, 0,0,0}, int(nxpmut*cov));
  196. shape l4("line", -1, {0,r+lpillar,0, 0,rfluid,0}, nair);
  197. shape q71("quadrangle", fluid, {l1,l2,l3,l4});
  198. shape l5 = q62.getsons()[2];
  199. shape l6("line", -1, {r,r+lpillar,0, r,sqrt(pow(rfluid,2)-pow(r,2)),0}, nair);
  200. 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)));
  201. shape q72("quadrangle", fluid, {l5,l6,l7,l2});
  202. shape l8 = q63.getsons()[2];
  203. shape l9("line", -1, {r+lpillar,r+lpillar,0, rfluid*sqrt(2)/2,rfluid*sqrt(2)/2,0}, nair);
  204. 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);
  205. shape q73("quadrangle", fluid, {l8,l9,l10,l6});
  206. shape l11("line", -1, {r+lpillar,0,0, rfluid,0,0}, nair);
  207. shape l12("arc", -1, {rfluid,0,0, rfluid*sqrt(2)/2,rfluid*sqrt(2)/2,0, 0,0,0}, int(nxpmut*(r+lpillar)/r));
  208. shape l13 = q63.getsons()[1];
  209. shape q74("quadrangle", fluid, {l11,l12,l9,l13});
  210. // Fluid boundary:
  211. shape fb1 = q71.getsons()[2];
  212. fb1.setphysicalregion(fluidboundary);
  213. shape fb2 = q72.getsons()[2];
  214. fb2.setphysicalregion(fluidboundary);
  215. shape fb3 = q73.getsons()[2];
  216. fb3.setphysicalregion(fluidboundary);
  217. shape fb4 = q74.getsons()[1];
  218. fb4.setphysicalregion(fluidboundary);
  219. // Provide to the mesh all shapes of interest:
  220. 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});
  221. return mymesh;
  222. }
  223. int main(void)
  224. {
  225. SlepcInitialize(0,{},0,0);
  226. sparselizard();
  227. SlepcFinalize();
  228. return 0;
  229. }