GaAs diode: transient with traps (1D).

(source code)

Simulating transient charge transport in a GaAs p-i-n diode with an electron trap.

module Ex109_Traps

using ChargeTransport
using ExtendableGrids
using PyPlot

# function to initialize the grid for a possble 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
    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)

    # set different regions in grid
    cellmask!(grid, [0.0 * μm],                 [h_pdoping],               regionAcceptor)  # p-doped
    cellmask!(grid, [h_pdoping],                [h_pdoping + h_intrinsic], regionIntrinsic) # intrinsic
    cellmask!(grid, [h_pdoping + h_intrinsic],  [h_total],                 regionDonor)     # n-doped

    bfacemask!(grid, [h_pdoping],               [h_pdoping],               bregionJunction1, tol = 1.0e-18)
    bfacemask!(grid, [h_pdoping + h_intrinsic], [h_pdoping + h_intrinsic], bregionJunction2, tol = 1.0e-18)

    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
    ################################################################################

    iphin            = 1 # index electron quasi Fermi potential
    iphip            = 2 # index hole quasi Fermi potential
    iphit            = 3 # index trap quasi Fermi potential
    numberOfCarriers = 3 # electrons, holes and traps

    # physical data
    Ec               = 1.424                             *  eV
    Ev               = 0.0                               *  eV
    Et               = 0.6                               *  eV
    Nc               = 4.351959895879690e17              / (cm^3)
    Nv               = 9.139615903601645e18              / (cm^3)
    Nt               = 1e16                              / (cm^3)
    mun              = 8500.0                            * (cm^2) / (V * s)
    mup              = 400.0                             * (cm^2) / (V * s)
    mut              = 0.0                               * (cm^2) / (V * s)  # such that there is no flux
    εr               = 12.9                              *  1.0              # relative dielectric permittivity of GAs
    T                = 300.0                             *  K

    # recombination parameters
    ni               = sqrt(Nc * Nv) * exp(-(Ec - Ev) / (2 * kB * T))        # intrinsic concentration
    n0               = Nc * Boltzmann( (Et-Ec) / (kB*T) )                    # Boltzmann equilibrium concentration
    p0               = ni^2 / n0                                             # Boltzmann equilibrium concentration
    Auger            = 1.0e-29                           * cm^6 / s          # 1.0e-41
    SRH_LifeTime     = 1.0e-3                            * ns
    Radiative        = 1.0e-10                           * cm^3 / s          # 1.0e-16
    G                = 1.0e25                            / (cm^3 * s)

    # doping -- trap doping will not be set and thus automatically zero
    dopingFactorNd   = 1.0
    dopingFactorNa   = 0.46
    Nd               = dopingFactorNd * Nc
    Na               = dopingFactorNa * Nv

    # contact voltage
    voltageAcceptor  = 1.5                               * V

    if test == false
        println("*** done\n")
    end
    ################################################################################
    if test == false
        println("Define System and fill in information about model")
    end
    ################################################################################

    # Initialize Data instance and fill in data
    data                               = Data(grid, numberOfCarriers)

    # Possible choices: Stationary, Transient
    data.modelType                     = Transient

    # Possible choices: Boltzmann, FermiDiracOneHalfBednarczyk, FermiDiracOneHalfTeSCA,
    # FermiDiracMinusOne, Blakemore
    data.F                            .= [FermiDiracOneHalfTeSCA, FermiDiracOneHalfTeSCA, FermiDiracMinusOne]

    data.bulkRecombination             = set_bulk_recombination(;iphin = iphin, iphip = iphip,
                                                                 bulk_recomb_Auger = true,
                                                                 bulk_recomb_radiative = true,
                                                                 bulk_recomb_SRH = true)

    # Here, we enable the traps and parse the respective index and the regions where the trap is defined.
    enable_trap_carrier!(;data = data, trapCarrier = iphit, regions = regions)

    # Possible choices: GenerationNone, GenerationUniform
    data.generationModel               = GenerationUniform

    # Possible choices: OhmicContact, SchottkyContact (outer boundary) and InterfaceNone,
    # InterfaceRecombination (inner boundary).
    data.boundaryType[bregionAcceptor] = OhmicContact
    data.boundaryType[bregionDonor]    = OhmicContact

    data.ohmicContactModel             = OhmicContactRobin

    # Choose 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
    ################################################################################

    params                                              = Params(grid, numberOfCarriers)

    params.temperature                                  = T
    params.UT                                           = (kB * params.temperature) / q
    params.chargeNumbers[iphin]                         = -1
    params.chargeNumbers[iphip]                         =  1
    params.chargeNumbers[iphit]                         = -1 # trap charge number determines whether hole or electron trap is used

    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.densityOfStates[iphit, ireg]             = Nt
        params.bandEdgeEnergy[iphin, ireg]              = Ec
        params.bandEdgeEnergy[iphip, ireg]              = Ev
        params.bandEdgeEnergy[iphit, ireg]              = Et
        params.mobility[iphin, ireg]                    = mun
        params.mobility[iphip, ireg]                    = mup
        params.mobility[iphit, ireg]                    = mut

        # recombination parameters
        params.recombinationRadiative[ireg]             = Radiative
        params.recombinationSRHLifetime[iphin, ireg]    = SRH_LifeTime
        params.recombinationSRHLifetime[iphip, ireg]    = SRH_LifeTime
        params.recombinationSRHTrapDensity[iphin, ireg] = n0
        params.recombinationSRHTrapDensity[iphip, ireg] = p0
        params.recombinationAuger[iphin, ireg]          = Auger
        params.recombinationAuger[iphip, ireg]          = Auger
        params.generationUniform[ireg]                  = G

    end

    # doping
    params.doping[iphin, regionDonor]                   = Nd
    params.doping[iphin, regionIntrinsic]               = ni
    params.doping[iphip, regionIntrinsic]               = 0.0
    params.doping[iphip, regionAcceptor]                = Na

    data.params                                         = params
    ctsys                                               = System(grid, data, unknown_storage=unknown_storage)

    if test == false
        show_params(ctsys)
        println("*** done\n")
    end
    ################################################################################
    if test == false
        println("Define control parameters for Solver")
    end
    ################################################################################

    control                = SolverControl()
    control.verbose        = verbose
    control.tol_round      = 1.0e-4
    control.damp_initial   = 0.5
    control.damp_growth    = 1.61
    control.max_round      = 3

    if test == false
        println("*** done\n")
    end

    ################################################################################
    if test == false
        println("Compute solution in thermodynamic equilibrium")
    end
    ################################################################################

    # solve thermodynamic equilibrium and update initial guess
    solution = equilibrium_solve!(ctsys, control = control)
    inival   = solution

    if test == false
        println("*** done\n")
    end

    if plotting
        label_solution, label_density, label_energy = set_plotting_labels(data)

        # add labels for traps
        label_energy[1, iphit] = "\$E_{\\tau}-q\\psi\$"; label_energy[2, iphit] = "\$ - q \\varphi_{\\tau}\$"
        label_density[iphit]   = "\$n_{\\tau}\$";        label_solution[iphit]  = "\$ \\varphi_{\\tau}\$"

        Plotter.figure()
        plot_energies(Plotter, ctsys, solution, "Equilibrium", label_energy)
        Plotter.figure()
        plot_densities(Plotter, ctsys, solution,"Equilibrium", label_density)
        Plotter.figure()
        plot_solution(Plotter, ctsys, solution, "Equilibrium", label_solution)
    end

    ################################################################################
    if test == false
        println("Loop for putting generation on")
    end
    ################################################################################

    # Scan rate and time steps
    scanrate      = 1.0 * V/s
    number_tsteps = 25
    endVoltage    = voltageAcceptor # bias goes until the given voltage at acceptor boundary

    # with fixed timestep sizes we can calculate the times
    # a priori
    tend          = endVoltage/scanrate
    tvalues       = range(0.0, stop = tend, length = number_tsteps)
    Δt            = tvalues[2] - tvalues[1]

these values are needed for putting the generation slightly on

    I             = collect(20:-1:0.0)
    LAMBDA        = 10 .^ (-I)

    IV            = zeros(0) # for IV values
    biasValues    = zeros(0) # for bias values

    for istep = 1:length(I)-1

        # turn slowly generation on
        data.λ2   = LAMBDA[istep + 1]

        if test == false
            println("increase generation with λ2 = $(data.λ2)")
        end

        solution = solve(ctsys, inival = inival, control = control)
        inival   = solution

    end # generation loop

    if test == false
        println("*** done\n")
    end
    ################################################################################
    if test == false
        println("IV Measurement loop")
    end
    ################################################################################

    for istep = 2:number_tsteps

        t  = tvalues[istep]          # Actual time
        Δu = t * scanrate            # Applied voltage
        Δt = t - tvalues[istep-1]    # Time step size

        # Apply new voltage: set non equilibrium boundary conditions
        set_contact!(ctsys, bregionAcceptor, Δu = Δu)

        if test == false
            println("time value: t = $(t) s")
        end

        solution = solve(ctsys, inival = inival, control = control, tstep = Δt)

        # get I-V data
        current  = get_current_val(ctsys, solution, inival, Δt)

        push!(IV, w_device * z_device * current)
        push!(biasValues, Δu)

        inival   = solution

    end # bias loop

    if test == false
        println("*** done\n")
    end

    # plot solution and IV curve
    if plotting
        Plotter.figure()
        plot_energies(Plotter, ctsys, solution, "bias \$\\Delta u\$ = $(endVoltage), \$ t=$(tvalues[number_tsteps])\$", label_energy)
        Plotter.figure()
        plot_densities(Plotter, ctsys, solution,"bias \$\\Delta u\$ = $(endVoltage), \$ t=$(tvalues[number_tsteps])\$", label_density)
        Plotter.figure()
        plot_solution(Plotter, ctsys, solution, "bias \$\\Delta u\$ = $(endVoltage), \$ t=$(tvalues[number_tsteps])\$", label_solution)
        Plotter.figure()
        plot_IV(Plotter, biasValues,IV, "bias \$\\Delta u\$ = $(biasValues[end])", plotGridpoints = true)
    end

    testval = sum(filter(!isnan, solution))/length(solution) # when using sparse storage, we get NaN values in solution
    return testval

    if test == false
        println("*** done\n")
    end

end #  main

function test()
    testval = 0.9699245385329192
    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.