GaAs diode (1D).
We simulate charge transport in a GaAs pin diode, where we use the van Roosbroeck system of equations as charge transport model. The unknowns are given by the quasi Fermi potentials of electrons and holes $\varphi_n$, $\varphi_p$ and the electric potential $\psi$. The simulations are performed out of equilibrium and for the stationary problem.
module Ex101_PIN
using ChargeTransport # drift-diffusion solver
using ExtendableGrids # grid initializer
using PyPlot # solution visualizer
# This function is used to initialize the grid for a possible extension to other p-i-n devices.
function initialize_pin_grid(refinementfactor, h_ndoping, h_intrinsic, h_pdoping)
coord_ndoping = collect(range(0.0, stop = h_ndoping, length = 3 * refinementfactor))
coord_intrinsic = collect(range(h_ndoping, stop = (h_ndoping + h_intrinsic), length = 3 * refinementfactor))
coord_pdoping = collect(range((h_ndoping + h_intrinsic), stop = (h_ndoping + h_intrinsic + h_pdoping), length = 3 * refinementfactor))
coord = glue(coord_ndoping, coord_intrinsic)
coord = glue(coord, coord_pdoping)
return coord
end
you can also use other Plotters, if you add them to the example file
function main(;n = 3, Plotter = PyPlot, plotting = false, verbose = false, test = false, unknown_storage=:sparse)
if plotting
Plotter.close("all")
end
################################################################################
if test == false
println("Set up grid and regions")
end
################################################################################
# region numbers
regionAcceptor = 1 # p doped region
regionIntrinsic = 2 # intrinsic region
regionDonor = 3 # n doped region
regions = [regionAcceptor, regionIntrinsic, regionDonor]
numberOfRegions = length(regions)
# boundary region numbers
# Note that by convention we have 1 for the left boundary and 2 for the right boundary. If
# adding additional interior boundaries, continue with 3, 4, ...
bregionAcceptor = 1
bregionDonor = 2
bregionJunction1 = 3
bregionJunction2 = 4
# grid
refinementfactor = 2^(n-1)
h_pdoping = 2.0 * μm
h_intrinsic = 2.0 * μm
h_ndoping = 2.0 * μm
h_total = h_pdoping + h_intrinsic + h_ndoping
w_device = 0.5 * μm # width of device
z_device = 1.0e-4 * cm # depth of device
coord = initialize_pin_grid(refinementfactor,
h_pdoping,
h_intrinsic,
h_ndoping)
grid = simplexgrid(coord)
# cellmask! for defining the subregions and assigning region number
cellmask!(grid, [0.0 * μm], [h_pdoping], regionAcceptor) # p-doped region = 1
cellmask!(grid, [h_pdoping], [h_pdoping + h_intrinsic], regionIntrinsic) # intrinsic region = 2
cellmask!(grid, [h_pdoping + h_intrinsic], [h_pdoping + h_intrinsic + h_ndoping], regionDonor) # n-doped region = 3
# bfacemask! for setting different boundary regions. At exterior boundaries they are
# automatically set by ExtendableGridsjl. Thus, there the following two lines are actually
# unneccesarry, but are only written for completeness.
bfacemask!(grid, [0.0], [0.0], bregionAcceptor) # outer left boundary
bfacemask!(grid, [h_total], [h_total], bregionDonor) # outer right boundary
bfacemask!(grid, [h_pdoping], [h_pdoping], bregionJunction1) # first inner interface
bfacemask!(grid, [h_pdoping + h_intrinsic], [h_pdoping + h_intrinsic], bregionJunction2) # second inner interface
if plotting
gridplot(grid, Plotter = Plotter, legend=:lt)
Plotter.title("Grid")
end
if test == false
println("*** done\n")
end
################################################################################
if test == false
println("Define physical parameters and model")
end
################################################################################
# set indices of the quasi Fermi potentials
iphin = 1 # electron quasi Fermi potential
iphip = 2 # hole quasi Fermi potential
numberOfCarriers = 2
We define the physical data.
Ec = 1.424 * eV
Ev = 0.0 * eV
Nc = 4.351959895879690e17 / (cm^3)
Nv = 9.139615903601645e18 / (cm^3)
mun = 8500.0 * (cm^2) / (V * s)
mup = 400.0 * (cm^2) / (V * s)
εr = 12.9 * 1.0 # relative dielectric permittivity of GAs
T = 300.0 * K
# recombination parameters
Auger = 1.0e-29 * cm^6 / s
SRH_TrapDensity = 1.0e10 / cm^3
SRH_LifeTime = 1.0 * ns
Radiative = 1.0e-10 * cm^3 / s
# doping
dopingFactorNd = 1.0
dopingFactorNa = 0.46
Nd = dopingFactorNd * Nc
Na = dopingFactorNa * Nv
# intrinsic concentration
ni = sqrt(Nc * Nv) * exp(-(Ec - Ev) / (2 * kB * T))
# contact voltage: we impose an applied voltage only on one boundary.
# At the other boundary the applied voltage is zero.
voltageAcceptor = 1.5 * V
if test == false
println("*** done\n")
end
################################################################################
if test == false
println("Define System and fill in information about model")
end
################################################################################
We initialize the Data instance and fill in predefined data.
data = Data(grid, numberOfCarriers)
# Following variable declares, if we want to solve stationary or transient problem
data.modelType = Stationary
# Following choices are possible for F: Boltzmann, FermiDiracOneHalfBednarczyk,
# FermiDiracOneHalfTeSCA, FermiDiracMinusOne, Blakemore
data.F .= Boltzmann
# Here, we need to specify which numbers are associated with electron and hole quasi
# Fermi potential. Further, the desired recombination processes can be chosen here.
# Note that, if you choose a SRH recombination you can further specify a transient SRH
# recombination by the method enable_trap_carrier! and adjusting the modelType. Otherwise, by
# default we use the stationary model for this type of recombination.
data.bulkRecombination = set_bulk_recombination(;iphin = iphin, iphip = iphip,
bulk_recomb_Auger = true,
bulk_recomb_radiative = true,
bulk_recomb_SRH = true)
# Following choices are possible for boundary model: For contacts currently only
# OhmicContact and SchottkyContact are possible. For inner boundaries we have
# InterfaceNone, InterfaceRecombination.
data.boundaryType[bregionAcceptor] = OhmicContact
data.boundaryType[bregionDonor] = OhmicContact
# Following choices are possible for the flux discretization scheme: ScharfetterGummel,
# ScharfetterGummelGraded, ExcessChemicalPotential, ExcessChemicalPotentialGraded,
# DiffusionEnhanced, GeneralizedSG
data.fluxApproximation .= ExcessChemicalPotential
if test == false
println("*** done\n")
end
################################################################################
if test == false
println("Define Params and fill in physical parameters")
end
################################################################################
Define the Params struct. Params contains all necessary physical parameters. If one wants to simulate space-dependent variables, one additionally needs to generate a ParamsNodal struct, see Ex102.
params = Params(grid, numberOfCarriers)
params.temperature = T
params.UT = (kB * params.temperature) / q
params.chargeNumbers[iphin] = -1
params.chargeNumbers[iphip] = 1
for ireg in 1:numberOfRegions # region data
params.dielectricConstant[ireg] = εr * ε0
# effective DOS, band-edge energy and mobilities
params.densityOfStates[iphin, ireg] = Nc
params.densityOfStates[iphip, ireg] = Nv
params.bandEdgeEnergy[iphin, ireg] = Ec
params.bandEdgeEnergy[iphip, ireg] = Ev
params.mobility[iphin, ireg] = mun
params.mobility[iphip, ireg] = mup
# recombination parameters
params.recombinationRadiative[ireg] = Radiative
params.recombinationSRHLifetime[iphin, ireg] = SRH_LifeTime
params.recombinationSRHLifetime[iphip, ireg] = SRH_LifeTime
params.recombinationSRHTrapDensity[iphin, ireg] = SRH_TrapDensity
params.recombinationSRHTrapDensity[iphip, ireg] = SRH_TrapDensity
params.recombinationAuger[iphin, ireg] = Auger
params.recombinationAuger[iphip, ireg] = Auger
end
# doping
params.doping[iphin, regionDonor] = Nd # data.doping = [0.0 Na;
params.doping[iphin, regionIntrinsic] = ni # ni 0.0;
params.doping[iphip, regionAcceptor] = Na # Nd 0.0]
Region dependent params is now a substruct of data which is again a substruct of the system and will be parsed in next step.
data.params = params
In the last step, we initialize our system with previous data which is likewise dependent on the parameters. It is important that this is in the end, otherwise our VoronoiFVMSys is not dependent on the data we initialized but rather on default data.
ctsys = System(grid, data, unknown_storage=unknown_storage)
if test == false
# Here we can show region dependent physical parameters. show_params() only supports
# region dependent parameters, but, if one wishes to print nodal dependent parameters,
# currently this is possible with println(ctsys.data.paramsnodal). We neglected here,
# since in most applications where the numberOfNodes is >> 10 this would results in a
# large output in the terminal.
show_params(ctsys)
println("*** done\n")
end
if plotting == true
################################################################################
println("Plot electroneutral potential, band-edge energies and doping")
################################################################################
# set legend for plotting routines. Either you can use the predefined labels or write your own.
label_solution, label_density, label_energy, label_BEE = set_plotting_labels(data)
psi0 = electroNeutralSolution(ctsys)
Plotter.figure()
plot_energies(Plotter, ctsys, label_BEE)
Plotter.figure()
plot_doping(Plotter, ctsys, label_density)
Plotter.figure()
plot_electroNeutralSolutionBoltzmann(Plotter, grid, psi0, ;plotGridpoints=true)
println("*** done\n")
end
################################################################################
if test == false
println("Define control parameters for Solver")
end
################################################################################
control = SolverControl()
control.verbose = verbose
control.maxiters = 50
control.abstol = 1.0e-14
control.reltol = 1.0e-14
control.tol_round = 1.0e-8
control.damp_initial = 0.5
control.max_round = 3
if test == false
println("*** done\n")
end
################################################################################
if test == false
println("Compute solution in thermodynamic equilibrium")
end
################################################################################
# calculate equilibrium solution and as initial guess
solution = equilibrium_solve!(ctsys, control = control)
inival = solution
if test == false
println("*** done\n")
end
################################################################################
if test == false
println("Bias loop")
end
################################################################################
maxBias = voltageAcceptor # bias goes until the given voltage at acceptor boundary
biasValues = range(0, stop = maxBias, length = 32)
IV = zeros(0)
for Δu in biasValues
if test == false
println("bias value: Δu = ", Δu, " V")
end
# set non equilibrium boundary conditions
set_contact!(ctsys, bregionAcceptor, Δu = Δu)
solution = solve(ctsys; inival = inival, control = control)
inival .= solution
# Note that the old way of solving will be soon removed (see current API changes in VoronoiFVM)
solve!(solution, inival, ctsys, control = control, tstep = Inf) inival .= solution
# get I-V data
current = get_current_val(ctsys, solution)
push!(IV, abs.(w_device * z_device * ( current)) )
end # bias loop
if test == false
println("*** done\n")
end
# plot solution and IV curve
if plotting
Plotter.figure()
plot_energies(Plotter, ctsys, solution, "Applied voltage Δu = $(biasValues[end])", label_energy, plotGridpoints = false)
Plotter.figure()
plot_solution(Plotter, ctsys, solution, "Applied voltage Δu = $(biasValues[end])", label_solution, plotGridpoints = true)
Plotter.figure()
plot_densities(Plotter, ctsys, solution, "Applied voltage Δu = $(biasValues[end])", label_density, plotGridpoints = true)
Plotter.figure()
plot_IV(Plotter, biasValues,IV, "Applied voltage Δu = $(biasValues[end])", plotGridpoints = true)
end
testval = solution[15]
return testval
if test == false
println("*** done\n")
end
end # main
function test()
testval = 1.5068426833371802
main(test = true, unknown_storage=:dense) ≈ testval && main(test = true, unknown_storage=:sparse) ≈ testval
end
if test == false
println("This message should show when the PIN module has successfully recompiled.")
end
end # module
This page was generated using Literate.jl.