├── examples └── input.json └── src ├── main.f90 ├── special_functions.f90 ├── view.f90 └── prop.f90 /examples/input.json: -------------------------------------------------------------------------------- 1 | { 2 | "airfoil_DB": ".", 3 | "case": "MyProp", 4 | "condition": { 5 | "velocity": 0.0, 6 | "density": 1.225 7 | }, 8 | "propeller": { 9 | "name": "dummy", 10 | "#blades": 2, 11 | "diameter": 0.2032, 12 | "pitch": 0.1143, 13 | "hub_radius": 0.01016, 14 | "root_chord": 0.1, 15 | "tip_chord": -0.02, 16 | "rotation": "CCW", 17 | "root_airfoil": { 18 | "name" : "NACA_2412", 19 | "properties": { 20 | "type": "linear", 21 | "alpha_L0" : -0.074996798, 22 | "CL_alpha" : 6.283185307, 23 | "Cm_L0" : -0.111476594, 24 | "Cm_alpha" : 0.0, 25 | "alpha_min_drag" : 0.0, 26 | "CD_min" : 0.0, 27 | "CL_max" : 1.4 28 | } 29 | }, 30 | "tip_airfoil": { 31 | "name" : "NACA_2412", 32 | "properties": { 33 | "type": "linear", 34 | "alpha_L0" : -0.074996798, 35 | "CL_alpha" : 6.283185307, 36 | "Cm_L0" : -0.111476594, 37 | "Cm_alpha" : 0.0, 38 | "alpha_min_drag" : 0.0, 39 | "CD_min" : 0.0, 40 | "CL_max" : 1.4 41 | } 42 | }, 43 | "grid": 25 44 | }, 45 | "motor": { 46 | "name": "dummy", 47 | "kv": 1090, 48 | "no_load_current": 0.69, 49 | "resistance": 0.235, 50 | "gear_ratio": 1.0, 51 | }, 52 | "battery": { 53 | "name": "dummy", 54 | "#cells": 1, 55 | "cell_capacity": 1600, 56 | "cell_no_load_voltage": 3.7, 57 | "cell_impedance": 0.0141, 58 | "c_rating": 1.0 59 | }, 60 | "esc": { 61 | "name": "dummy", 62 | "resistance": 0.015 63 | }, 64 | "run": { 65 | "rpm": {"value": 1000.0}, 66 | "thrust": {"value": 1.0}, 67 | "throttle": {"value": 1.0}, 68 | "sequence" : { 69 | "filename" : "diameter_sequence_test.txt", 70 | "variable": "diameter", 71 | "other" : ["pitch","diameter","kv"], 72 | "start": 0.1016, 73 | "end": 0.3048, 74 | "steps": 11, 75 | "holding": "thrust", 76 | "value": 1.4 77 | }, 78 | "sequence" : { 79 | "variable": "pitch", 80 | "start": 0.05715, 81 | "end": 0.17145, 82 | "steps": 11, 83 | "holding": "thrust", 84 | "value": 1.4 85 | }, 86 | "sequence" : { 87 | "variable": "kv", 88 | "start": 545, 89 | "end": 1635, 90 | "steps": 11, 91 | "holding": "thrust", 92 | "value": 1.4 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main.f90: -------------------------------------------------------------------------------- 1 | !gfortran -fdefault-real-8 json.f90 math.f90 dataset.f90 airfoil.f90 section.f90 prop.f90 view.f90 main.f90 2 | program main 3 | use view_m 4 | use special_functions_m 5 | use prop_m 6 | implicit none 7 | type(prop_t) :: myprop 8 | character(100) :: filename 9 | type(json_value),pointer :: json_run, json_command 10 | integer :: error = 0 11 | integer :: i,nrun_types,dorun 12 | 13 | real :: time1,time2 14 | 15 | call cpu_time(time1) 16 | write(*,*) '-----------------------------------------------' 17 | write(*,*) '| |' 18 | write(*,*) '| BBBBB |' 19 | write(*,*) '| BB BB |' 20 | write(*,*) '| BB BB |' 21 | write(*,*) '| BB BB |' 22 | write(*,*) '| BB BB |' 23 | write(*,*) '| BB BB |' 24 | write(*,*) '| BB BB |' 25 | write(*,*) '| BB BBBBBB |' 26 | write(*,*) '| |' 27 | write(*,*) '| BladeX 1.0 |' 28 | write(*,*) '| |' 29 | write(*,*) '| (c) USU Aero Lab 2016 |' 30 | write(*,*) '| |' 31 | write(*,*) '| This software comes with |' 32 | write(*,*) '| ABSOLUTELY NO WARRANTY EXPRESSED OR IMPLIED |' 33 | write(*,*) '| |' 34 | write(*,*) '| Submit bug reports to: |' 35 | write(*,*) '| doug.hunsaker@usu.edu |' 36 | write(*,*) '-----------------------------------------------' 37 | write(*,*) 38 | 39 | call get_command_argument(1,filename) 40 | myprop%master_filename = filename 41 | 42 | call prop_set_defaults(myprop) 43 | call prop_load_json(myprop,error) 44 | 45 | if(error.eq.1) STOP 46 | call prop_init_setup(myprop) 47 | 48 | call myprop%json%get('run', json_run) 49 | nrun_types = json_value_count(json_run) 50 | ! write(*,*) 'Number of commands to run : ',nrun_types 51 | 52 | do i=1,nrun_types 53 | call json_value_get(json_run,i,json_command) 54 | run_type = trim(json_command%name) 55 | dorun = 1 56 | call json_get(json_command,'run',dorun,json_found) 57 | if(json_failed()) dorun = 1; !automatically run command if .run sub-command doesn't exist 58 | call json_clear_exceptions() 59 | 60 | if(dorun .eq. 1) then 61 | write(*,*) 62 | write(*,*) 'Running command : ',run_type 63 | 64 | select case (run_type) 65 | case ('stl') 66 | call view_stl(myprop) 67 | case ('rpm') 68 | call prop_run_rpm(myprop,json_required_real(json_command,'value')) 69 | case ('throttle') 70 | call prop_run_throttle(myprop,json_required_real(json_command,'value')) 71 | case ('thrust') 72 | call prop_run_thrust(myprop,json_required_real(json_command,'value')) 73 | case ('sequence') 74 | call sf_sequence(myprop,json_command) 75 | ! case ('thrustplots') 76 | ! call sf_thrust_plots(myprop,json_command) 77 | case default 78 | write(*,*) 'Command not recognized.' 79 | end select 80 | end if 81 | end do 82 | 83 | call prop_deallocate(myprop) 84 | 85 | call cpu_time(time2) 86 | write(*,*) 'CPU time total (sec): ',time2-time1 87 | 88 | ! filename = 'out.json' 89 | ! call prop_write_json_file(myprop,filename) 90 | 91 | end program main 92 | -------------------------------------------------------------------------------- /src/special_functions.f90: -------------------------------------------------------------------------------- 1 | module special_functions_m 2 | use prop_m 3 | implicit none 4 | 5 | contains 6 | 7 | !----------------------------------------------------------------------------------------------------------- 8 | subroutine sf_thrust_plots(t,json_command) 9 | use prop_m 10 | implicit none 11 | type(prop_t) :: t 12 | type(json_value),intent(in),pointer :: json_command 13 | type(json_value),pointer :: jp 14 | integer :: nsteps = 11 15 | real :: temp,mult 16 | character(100) :: filename 17 | integer :: ierror 18 | 19 | mult = 0.5 20 | 21 | nullify(jp) 22 | call json_value_create(jp) 23 | 24 | filename = trim(adjustl(t%master_filename))//'_thrust_plots.txt' 25 | open(unit = 10, File = filename, action = 'write', iostat = ierror) 26 | write(10,*) ' Diameter[m] Thrust[N] Throttle PowerRequired[W] & 27 | &Current[A] SystemEfficiency Endurance[min] PropellerRPM' 28 | temp = t%diameter 29 | ! call sf_run_sequence(t,'diameter','thrust',(1.0-mult)*t%diameter,(1.0+mult)*t%diameter,nsteps) 30 | t%diameter = temp 31 | 32 | write(10,*) 33 | write(10,*) ' Pitch[m] Thrust[N] Throttle PowerRequired[W] & 34 | &Current[A] SystemEfficiency Endurance[min] PropellerRPM' 35 | temp = t%pitch 36 | ! call sf_run_sequence(t,'pitch','thrust',(1.0-mult)*t%pitch,(1.0+mult)*t%pitch,nsteps) 37 | t%pitch = temp 38 | 39 | write(10,*) 40 | write(10,*) ' Kv[RPM/V] Thrust[N] Throttle PowerRequired[W] & 41 | &Current[A] SystemEfficiency Endurance[min] PropellerRPM' 42 | temp = t%motor%kv 43 | ! call sf_run_sequence(t,'kv',(1.0-mult)*t%motor%kv,(1.0+mult)*t%motor%kv,nsteps,'thrust',1.0) !fix this 44 | t%motor%kv = temp 45 | 46 | close(10) 47 | end subroutine sf_thrust_plots 48 | 49 | !----------------------------------------------------------------------------------------------------------- 50 | subroutine sf_sequence(t,json_command) 51 | use prop_m 52 | implicit none 53 | type(prop_t) :: t 54 | type(json_value),intent(in),pointer :: json_command 55 | character(len=:),allocatable :: cval,varname,runtype 56 | character(100) :: filename 57 | real :: xval,xstart,xend,results(6),runvalue 58 | integer :: nsteps,ierror 59 | 60 | call json_get(json_command,'variable', varname, json_found); call json_check(); 61 | xstart = json_required_real(json_command,'start') 62 | xend = json_required_real(json_command,'end') 63 | nsteps = json_optional_integer(json_command,'steps',10) 64 | call json_get(json_command,'holding', runtype, json_found); call json_check(); 65 | runvalue = json_required_real(json_command,'value') 66 | 67 | !Get filename if specified 68 | call json_get(json_command,'filename', cval,json_found); 69 | if(json_failed() .or. (trim(cval).eq.'')) then !No filename specified 70 | call json_clear_exceptions() 71 | filename = trim(adjustl(t%master_filename))//'_'//trim(adjustl(varname))//'_sequence.txt' 72 | else 73 | filename = trim(cval) 74 | end if 75 | 76 | open(unit = 10, File = filename, action = 'write', iostat = ierror) 77 | if(varname.eq.'diameter') then 78 | write(10,*) ' Diameter[m] Thrust[N] Throttle PowerRequired[W] & 79 | &Current[A] SystemEfficiency Endurance[min] PropellerRPM' 80 | end if 81 | if(varname.eq.'pitch') then 82 | write(10,*) ' Pitch[m] Thrust[N] Throttle PowerRequired[W] & 83 | &Current[A] SystemEfficiency Endurance[min] PropellerRPM' 84 | end if 85 | if(varname.eq.'kv') then 86 | write(10,*) ' Kv[RPM/V] Thrust[N] Throttle PowerRequired[W] & 87 | &Current[A] SystemEfficiency Endurance[min] PropellerRPM' 88 | end if 89 | 90 | call sf_run_sequence(t,varname,xstart,xend,nsteps,runtype,runvalue) 91 | 92 | close(10) 93 | end subroutine sf_sequence 94 | 95 | !----------------------------------------------------------------------------------------------------------- 96 | subroutine sf_run_sequence(t,varname,xstart,xend,nsteps,runtype,runvalue) 97 | use prop_m 98 | implicit none 99 | type(prop_t) :: t 100 | ! type(json_value),pointer,intent(in):: jp 101 | character(len=*),intent(in) :: varname,runtype 102 | real :: xval,xstart,xend,xinc,runvalue 103 | integer :: i,nsteps 104 | real,allocatable,dimension(:,:) :: results 105 | real :: temp_diameter,temp_pitch,temp_kv 106 | 120 Format(8ES25.13) 107 | 108 | !Store current settings 109 | temp_diameter = t%diameter 110 | temp_pitch = t%pitch 111 | temp_kv = t%motor%kv 112 | 113 | allocate(results(8,nsteps)) 114 | xinc = (xend-xstart)/real(nsteps-1) 115 | xval = xstart 116 | do i=1,nsteps 117 | results(1,i) = xval 118 | call sf_set_variable(t,varname,xval) 119 | call sf_run_single(t,runtype,runvalue,results(2:8,i)) 120 | xval = xval + xinc 121 | end do 122 | 123 | do i=1,nsteps 124 | write(10,120) results(:,i) 125 | end do 126 | 127 | !restore previous settings 128 | t%diameter = temp_diameter 129 | t%pitch = temp_pitch 130 | t%motor%kv = temp_kv 131 | 132 | end subroutine sf_run_sequence 133 | 134 | !----------------------------------------------------------------------------------------------------------- 135 | subroutine sf_set_variable(t,varname,value) 136 | use prop_m 137 | implicit none 138 | type(prop_t) :: t 139 | character(len=*),intent(in) :: varname 140 | real :: value 141 | 142 | if(varname.eq.'diameter') t%diameter = value 143 | if(varname.eq.'pitch') t%pitch = value 144 | if(varname.eq.'kv') t%motor%kv = value 145 | end subroutine sf_set_variable 146 | 147 | !----------------------------------------------------------------------------------------------------------- 148 | subroutine sf_run_single(t,runtype,runvalue,results) 149 | use prop_m 150 | implicit none 151 | type(prop_t) :: t 152 | character(len=*),intent(in) :: runtype 153 | real :: results(7),runvalue 154 | 155 | call prop_setup(t) 156 | select case (runtype) 157 | case ('rpm') 158 | call prop_run_rpm(t,runvalue) 159 | case ('throttle') 160 | call prop_run_throttle(t,runvalue) 161 | case ('thrust') 162 | call prop_run_thrust(t,runvalue) 163 | end select 164 | results(:) = 0.0 165 | 166 | results(1) = t%Thrust 167 | results(2) = t%throttle 168 | results(3) = t%battery%power 169 | results(4) = t%battery%current 170 | results(5) = t%eta 171 | results(6) = t%battery%endurance 172 | results(7) = t%rpm 173 | end subroutine sf_run_single 174 | 175 | end module special_functions_m -------------------------------------------------------------------------------- /src/view.f90: -------------------------------------------------------------------------------- 1 | module view_m 2 | use prop_m 3 | implicit none 4 | 5 | contains 6 | 7 | 8 | !----------------------------------------------------------------------------------------------------------- 9 | 10 | subroutine view_stl(t) 11 | type(prop_t) :: t 12 | type(section_t),pointer :: si 13 | type(airfoil_t),pointer :: af1 14 | type(airfoil_t),pointer :: af2 15 | real,allocatable,dimension(:,:) :: af_points1,af_points2 16 | character(100) :: filename 17 | integer :: i,isec,af_datasize,iblade 18 | integer :: ierror = 0 19 | 20 | do i=1,size(airfoils) 21 | af1 => airfoils(i); 22 | call af_create_geom_from_file(af1,DB_Airfoil) 23 | end do 24 | af1 => t%af1 25 | af2 => t%af2 26 | 27 | filename = trim(adjustl(t%master_filename))//'_view.stl' 28 | open(unit = 10, File = filename, action = 'write', iostat = ierror) 29 | 30 | ! filename = trim(adjustl(t%master_filename))//'_view.web' 31 | ! open(unit = 20, File = filename, action = 'write', iostat = ierror) 32 | 33 | write(10,'(A)') 'solid geom' 34 | 35 | af_datasize = af1%geom%datasize 36 | if(af2%geom%datasize .ne. af_datasize) then 37 | write(*,*) 'Both root and tip airfoils must have same number of nodes' 38 | stop 39 | end if 40 | allocate(af_points1(af_datasize,3)) 41 | allocate(af_points2(af_datasize,3)) 42 | 43 | do iblade=1,t%nblades 44 | do isec=1,t%nSec 45 | si => t%sec(isec) 46 | call view_create_local_airfoil(af1,af2,t%side,si%percent_1,si%chord_1,& 47 | & si%twist1,si%dihedral1,af_datasize,si%P1(:),af_points1) 48 | call view_create_local_airfoil(af1,af2,t%side,si%percent_2,si%chord_2,& 49 | & si%twist2,si%dihedral2,af_datasize,si%P2(:),af_points2) 50 | 51 | call view_rotate_z(real(iblade-1)*2.0*pi/real(t%nblades),af_datasize,af_points1) 52 | call view_rotate_z(real(iblade-1)*2.0*pi/real(t%nblades),af_datasize,af_points2) 53 | 54 | call view_create_stl_shell(af_datasize,af_points1,af_points2) 55 | end do 56 | 57 | if(t%side == 'right') then 58 | si => t%sec(1) 59 | call view_create_local_airfoil(af1,af2,t%side,si%percent_1,si%chord_1,& 60 | & si%twist1,si%dihedral1,af_datasize,si%P1(:),af_points1) 61 | si => t%sec(t%nSec) 62 | call view_create_local_airfoil(af1,af2,t%side,si%percent_2,si%chord_2,& 63 | & si%twist2,si%dihedral2,af_datasize,si%P2(:),af_points2) 64 | else 65 | si => t%sec(1) 66 | call view_create_local_airfoil(af1,af2,t%side,si%percent_2,si%chord_2,& 67 | & si%twist2,si%dihedral2,af_datasize,si%P2(:),af_points2) 68 | si => t%sec(t%nSec) 69 | call view_create_local_airfoil(af1,af2,t%side,si%percent_1,si%chord_1,& 70 | & si%twist1,si%dihedral1,af_datasize,si%P1(:),af_points1) 71 | end if 72 | 73 | ! if(t%is_linear.eq.1) call view_create_stl_shell(af_datasize,af_points1,af_points2) 74 | ! call view_create_stl_rib(af_datasize,af_points1) 75 | ! call view_create_stl_rib(af_datasize,af_points2) 76 | end do 77 | 78 | write(10,*) 'endsolid geom' 79 | close(10) 80 | ! close(20) 81 | end subroutine view_stl 82 | 83 | !----------------------------------------------------------------------------------------------------------- 84 | subroutine view_rotate_z(theta,datasize,af_points) 85 | integer :: datasize,i 86 | real :: theta,af_points(datasize,3),point(3),phi,radius 87 | 88 | do i=1,datasize 89 | point(:) = af_points(i,:) 90 | phi = atan2(point(2),point(1)) 91 | radius = sqrt(point(1)**2 + point(2)**2) 92 | af_points(i,1) = radius*cos(theta+phi) 93 | af_points(i,2) = radius*sin(theta+phi) 94 | end do 95 | 96 | end subroutine view_rotate_z 97 | 98 | !----------------------------------------------------------------------------------------------------------- 99 | subroutine view_create_stl_shell(datasize,af_points1,af_points2) 100 | integer :: datasize,i 101 | real :: af_points1(datasize,3),af_points2(datasize,3) 102 | real :: P1(3),P2(3),P3(3) 103 | 104 | !Outer surface 105 | do i=1,datasize-1 106 | P1(:) = af_points1(i,:) 107 | P2(:) = af_points2(i,:) 108 | P3(:) = af_points1(i+1,:) 109 | 110 | call view_add_stl_triangle(P1,P3,P2) 111 | 112 | P1 = P2 113 | P2(:) = af_points2(i+1,:) 114 | call view_add_stl_triangle(P1,P3,P2) 115 | end do 116 | !Close Surface 117 | i = datasize 118 | P1(:) = af_points1(i,:) 119 | P2(:) = af_points1(1,:) 120 | P3(:) = af_points2(i,:) 121 | 122 | call view_add_stl_triangle(P1,P3,P2) 123 | 124 | P1 = P2 125 | P2(:) = af_points2(1,:) 126 | call view_add_stl_triangle(P1,P3,P2) 127 | end subroutine view_create_stl_shell 128 | 129 | !----------------------------------------------------------------------------------------------------------- 130 | subroutine view_create_stl_rib(datasize,af_points) 131 | integer :: datasize,i 132 | real :: af_points(datasize,3) 133 | real :: P1(3),P2(3),P3(3) 134 | 135 | do i=1,(datasize-2)/2 136 | P1(:) = af_points(i,:) 137 | P2(:) = af_points(datasize-i+1,:) 138 | P3(:) = af_points(i+1,:) 139 | call view_add_stl_triangle(P1,P3,P2) 140 | 141 | P1 = P2 142 | P2(:) = af_points(datasize-i,:) 143 | call view_add_stl_triangle(P1,P3,P2) 144 | end do 145 | end subroutine view_create_stl_rib 146 | 147 | !----------------------------------------------------------------------------------------------------------- 148 | subroutine view_add_stl_triangle(P1,P2,P3) 149 | real :: P1(3),P2(3),P3(3),norm(3) 150 | 110 Format(A15, 3ES25.13) 151 | 120 Format(9ES25.13) 152 | 153 | call math_plane_normal(P1,P2,P3,norm) 154 | write(10,110) 'facet normal ',norm(:) 155 | write(10,*) 'outer loop' 156 | write(10,110) 'vertex ',P1(:) 157 | write(10,110) 'vertex ',P2(:) 158 | write(10,110) 'vertex ',P3(:) 159 | write(10,*) 'endloop' 160 | write(10,*) 'endfacet' 161 | 162 | ! write(20,120) P1(:),P2(:),P3(:) 163 | end subroutine view_add_stl_triangle 164 | 165 | !----------------------------------------------------------------------------------------------------------- 166 | subroutine view_create_local_airfoil(af1,af2,side,percent,chord,twist,dihedral,datasize,point,output) 167 | type(airfoil_t),pointer :: af1 168 | type(airfoil_t),pointer :: af2 169 | character(5) :: side 170 | real :: percent,chord,twist,dihedral 171 | integer :: datasize,i 172 | real :: point(3),output(datasize,3) 173 | 174 | output(:,1:2) = af1%geom%RawData(:,:) + percent*(af2%geom%RawData(:,:) - af1%geom%RawData(:,:)) 175 | output(:,1) = output(:,1) - 0.25 176 | output = chord*output 177 | output(:,1) = -output(:,1) !flip x 178 | output(:,3) = -output(:,2) !assign y to z 179 | output(:,2) = 0.0 !set y to zero 180 | 181 | 182 | if(side.eq.'left') then 183 | ! twist = -twist 184 | dihedral = -dihedral 185 | end if 186 | 187 | do i=1,datasize 188 | call math_rot_y(output(i,:),twist) 189 | call math_rot_x(output(i,:),-dihedral) 190 | output(i,:) = output(i,:) + point(:) 191 | end do 192 | 193 | end subroutine view_create_local_airfoil 194 | 195 | end module view_m 196 | -------------------------------------------------------------------------------- /src/prop.f90: -------------------------------------------------------------------------------- 1 | module prop_m 2 | use myjson_m 3 | use section_m 4 | implicit none 5 | 6 | type motor_t 7 | integer :: ID 8 | character(20) :: name 9 | real :: kv ! kv rpm/V 10 | real :: Io ! No Load Current (Amps) 11 | real :: resistance ! Resistance (Ohms) 12 | real :: gearratio !gear ratio 13 | real :: rpm 14 | real :: torque !Torque (N-m) 15 | real :: current !current (Amps) 16 | real :: voltage !voltage (Volts) 17 | real :: brakepower !Break Power (Watts) 18 | end type motor_t 19 | 20 | type battery_t 21 | integer :: ID 22 | character(20) :: name 23 | integer :: ncells 24 | real :: capacity !mAh 25 | real :: Eo !No Load Voltage 26 | real :: resistance !Resistance (Ohms) 27 | real :: crating !C-rating 28 | real :: voltage !voltage (Volts) 29 | real :: current !current (Amps) 30 | real :: power ! power (Watts) 31 | real :: endurance !(minutes) 32 | end type battery_t 33 | 34 | 35 | type speedcontrol_t 36 | integer :: ID 37 | character(20) :: name 38 | real :: resistance !Ohms 39 | real :: eta !efficiency 40 | end type speedcontrol_t 41 | 42 | type prop_t 43 | integer :: ID 44 | character(10) :: name 45 | character(100) :: master_filename 46 | 47 | type(json_file) :: json !the JSON structure read from the file: 48 | 49 | integer :: verbose !=1 for write info, =0 for don't write info 50 | 51 | integer :: nblades 52 | real :: diameter 53 | real :: pitch 54 | real :: hubradius 55 | real :: chord_1 56 | real :: chord_2 57 | character(3) :: rotation 58 | character(5) :: side 59 | 60 | integer :: nSec 61 | type(section_t),pointer,dimension(:) :: sec 62 | 63 | character(100) :: af1_text,af2_text !original text from file 64 | type(airfoil_t),pointer :: af1 65 | type(airfoil_t),pointer :: af2 66 | 67 | 68 | type(motor_t) :: motor 69 | type(battery_t) :: battery 70 | type(speedcontrol_t) :: esc 71 | 72 | real :: velocity 73 | real :: density 74 | 75 | 76 | real :: rpm 77 | real :: omega !rad/s 78 | real :: Thrust,Torque,Power 79 | real :: C_thrust,C_torque,C_power 80 | 81 | !possible files 82 | character(100) :: f_pitch, f_chord, f_root_airfoil, f_tip_airfoil 83 | 84 | !Other Geometry Specs 85 | real :: root(3) 86 | real :: tip(3) 87 | real :: span 88 | real :: area 89 | 90 | real :: throttle 91 | real :: eta !system efficiency 92 | 93 | end type prop_t 94 | 95 | character(20) :: run_type !needed to know how to calculate sytem properties in prop_forces 96 | 97 | !Airfoil Database 98 | character(200) :: DB_Airfoil 99 | type(airfoil_t),pointer,dimension(:) :: airfoils 100 | 101 | contains 102 | 103 | !----------------------------------------------------------------------------------------------------------- 104 | subroutine prop_allocate(t) 105 | type(prop_t) :: t 106 | ! if(size(t%sec).gt.1) call prop_deallocate(t) 107 | 108 | allocate(t%sec(t%nSec)) 109 | end subroutine prop_allocate 110 | 111 | !----------------------------------------------------------------------------------------------------------- 112 | subroutine prop_deallocate(t) 113 | type(prop_t) :: t 114 | deallocate(t%sec) 115 | call t%json%destroy() 116 | end subroutine prop_deallocate 117 | 118 | !----------------------------------------------------------------------------------------------------------- 119 | subroutine prop_set_defaults(t) 120 | type(prop_t) :: t 121 | 122 | 123 | end subroutine prop_set_defaults 124 | 125 | !----------------------------------------------------------------------------------------------------------- 126 | subroutine prop_load_json(t,error) 127 | type(prop_t) :: t 128 | type(json_value),pointer :: j_afprop 129 | character(len=:),allocatable :: cval 130 | integer :: error,iairfoil,loc 131 | 132 | call json_initialize() 133 | 134 | ! write(*,*) 'reading input file: ',t%master_filename 135 | call t%json%load_file(filename = t%master_filename); call json_check() 136 | loc = index(t%master_filename,'.json') 137 | t%master_filename = t%master_filename(1:loc-1) !deletes the .json file extension 138 | 139 | write(*,*) 'Reading Input File...' 140 | 141 | ! call t%json%print_file() 142 | 143 | call t%json%get('airfoil_DB', cval); call json_check(); DB_Airfoil = trim(cval) 144 | write(*,*) 'Airfoil database located at: ',trim(DB_Airfoil) 145 | 146 | t%velocity = json_file_required_real(t%json,'condition.velocity') 147 | t%density = json_file_optional_real(t%json,'condition.density',1.225) 148 | 149 | call t%json%get('propeller.name', cval); call json_check(); t%name = trim(cval) 150 | call t%json%get('propeller.#blades', t%nblades); call json_check() 151 | call t%json%get('propeller.diameter', t%diameter); call json_check() 152 | call t%json%get('propeller.pitch', t%pitch); call json_check() 153 | call t%json%get('propeller.hub_radius', t%hubradius); call json_check() 154 | call t%json%get('propeller.root_chord', t%chord_1); call json_check() 155 | call t%json%get('propeller.tip_chord', t%chord_2); call json_check() 156 | call t%json%get('propeller.rotation', cval); call json_check(); t%rotation = trim(cval) 157 | call t%json%get('propeller.grid', t%nSec); call json_check() 158 | 159 | allocate(airfoils(2)) 160 | iairfoil = 0 161 | 162 | !Root Airfoil 163 | iairfoil = iairfoil + 1 164 | call t%json%get('propeller.root_airfoil.name', cval); call json_check() 165 | t%af1_text = trim(cval) 166 | airfoils(iairfoil)%name = trim(t%af1_text) 167 | call t%json%get('propeller.root_airfoil.properties', j_afprop); 168 | if(json_failed()) then !Read from airfoil database 169 | call json_clear_exceptions() 170 | call prop_load_airfoil(t,iairfoil,'',0) 171 | else !Read from local file 172 | call prop_load_airfoil(t,iairfoil,'propeller.root_airfoil.',1) 173 | end if 174 | t%af1 => airfoils(iairfoil) 175 | 176 | !Tip Airfoil 177 | iairfoil = iairfoil + 1 178 | call t%json%get('propeller.tip_airfoil.name', cval); call json_check() 179 | t%af2_text = trim(cval) 180 | airfoils(iairfoil)%name = trim(t%af2_text) 181 | call t%json%get('propeller.tip_airfoil.properties', j_afprop); 182 | if(json_failed()) then !Read from airfoil database 183 | call json_clear_exceptions() 184 | call prop_load_airfoil(t,iairfoil,'',0) 185 | else !Read from local file 186 | call prop_load_airfoil(t,iairfoil,'propeller.tip_airfoil.',1) 187 | end if 188 | t%af2 => airfoils(iairfoil) 189 | 190 | 191 | t%f_pitch = 'none' 192 | t%f_chord = 'none' 193 | t%f_root_airfoil = 'none' 194 | t%f_tip_airfoil = 'none' 195 | t%side = 'right' 196 | if(t%rotation.eq.'CW') t%side = 'left' 197 | 198 | call t%json%get('motor.name', cval); call json_check(); t%motor%name = trim(cval) 199 | call t%json%get('motor.kv', t%motor%kv); call json_check() 200 | call t%json%get('motor.no_load_current',t%motor%Io); call json_check() 201 | call t%json%get('motor.resistance', t%motor%resistance); call json_check() 202 | call t%json%get('motor.gear_ratio', t%motor%gearratio); call json_check() 203 | 204 | call t%json%get('battery.name', cval); call json_check(); t%battery%name = trim(cval) 205 | call t%json%get('battery.#cells', t%battery%ncells); call json_check() 206 | call t%json%get('battery.cell_capacity', t%battery%capacity); call json_check() 207 | call t%json%get('battery.cell_no_load_voltage',t%battery%Eo); call json_check() 208 | call t%json%get('battery.cell_impedance', t%battery%resistance); call json_check() 209 | call t%json%get('battery.c_rating', t%battery%crating); call json_check() 210 | 211 | call t%json%get('esc.name', cval); call json_check(); t%esc%name = trim(cval) 212 | call t%json%get('esc.resistance', t%esc%resistance); call json_check() 213 | 214 | end subroutine prop_load_json 215 | 216 | !----------------------------------------------------------------------------------------------------------- 217 | subroutine prop_load_airfoil(t,i,prefix,local) 218 | type(prop_t) :: t 219 | type(json_file) :: f_json !the JSON structure read from the file: 220 | character(len=*) :: prefix 221 | integer :: local 222 | integer :: i,ios 223 | character(100) :: fn,datafilename 224 | character(len=:),allocatable :: cval 225 | 226 | if(local.eq.1) then 227 | fn = trim(t%master_filename)//'.json' 228 | else 229 | fn = trim(adjustl(DB_Airfoil))//'/'//trim(adjustl(airfoils(i)%name))//'.json' 230 | end if 231 | 232 | write(*,*) 'Reading airfoil properties from file: ',trim(fn) 233 | call f_json%load_file(filename = trim(fn)); call json_check() 234 | ! call f_json%print_file() 235 | 236 | call f_json%get(trim(prefix)//'properties.type', cval); call json_check(); airfoils(i)%properties_type = trim(cval) 237 | 238 | select case (airfoils(i)%properties_type) 239 | case ('linear') 240 | airfoils(i)%aL0 = json_file_required_real(f_json,trim(prefix)//'properties.alpha_L0'); 241 | airfoils(i)%CLa = json_file_required_real(f_json,trim(prefix)//'properties.CL_alpha'); 242 | airfoils(i)%CmL0 = json_file_required_real(f_json,trim(prefix)//'properties.Cm_L0'); 243 | airfoils(i)%Cma = json_file_required_real(f_json,trim(prefix)//'properties.Cm_alpha'); 244 | airfoils(i)%CD0 = json_file_required_real(f_json,trim(prefix)//'properties.CD_min'); 245 | airfoils(i)%CLmax = json_file_optional_real(f_json,trim(prefix)//'properties.CL_max',-1.0); 246 | airfoils(i)%has_data_file = 0 247 | case ('datafile') 248 | call f_json%get(trim(prefix)//'properties.filename', cval); call json_check(); datafilename = trim(cval) 249 | datafilename = trim(adjustl(DB_Airfoil))//'/'//trim(adjustl(datafilename)) 250 | call af_create_from_data_file(airfoils(i),datafilename) 251 | airfoils(i)%has_data_file = 1 252 | case default 253 | write(*,*) 'Invalid airfoil properties type! Aborting program.' 254 | stop 255 | end select 256 | 257 | write(*,*) 'Loaded airfoil: ',airfoils(i)%name 258 | end subroutine prop_load_airfoil 259 | 260 | !----------------------------------------------------------------------------------------------------------- 261 | subroutine prop_write_json_file(t,filename) 262 | implicit none 263 | type(prop_t) :: t 264 | type(json_value),pointer :: p_root, p_condition, p_prop, p_motor, p_batt, p_esc, p_run 265 | character(100) :: filename 266 | integer :: ios,iairfoil,i,iunit 267 | 268 | iairfoil = 0 269 | ios = 0 270 | 271 | !root: 272 | call json_value_create(p_root) ! create the value and associate the pointer 273 | call to_object(p_root,trim(filename)) ! add the file name as the name of the overall structure 274 | call json_value_add(p_root, 'case', 'ABCDEFG') 275 | 276 | write(*,'(A)') '' 277 | write(*,'(A)') 'initialize the structure...' 278 | 279 | !condition structure: 280 | call json_value_create(p_condition) !an object 281 | call to_object(p_condition,'condition') 282 | call json_value_add(p_root, p_condition) 283 | call add_real_to_json_obj( p_condition, 'velocity', t%velocity, 'm/s') 284 | call add_real_to_json_obj( p_condition, 'density', t%density, 'kg/m^3') 285 | nullify(p_condition) 286 | 287 | !propeller structure: 288 | call json_value_create( p_prop) 289 | call to_object( p_prop, 'propeller') 290 | call json_value_add(p_root, p_prop) 291 | call json_value_add( p_prop, 'name', trim(t%name)) 292 | call add_int_to_json_obj( p_prop, '#blades', t%nblades, '') 293 | call add_real_to_json_obj( p_prop, 'diameter', t%diameter, 'm') 294 | call add_real_to_json_obj( p_prop, 'pitch', t%pitch, 'm') 295 | call add_real_to_json_obj( p_prop, 'hub_radius', t%hubradius, 'm') 296 | call add_real_to_json_obj( p_prop, 'root_chord', t%chord_1, 'm') 297 | call add_real_to_json_obj( p_prop, 'tip_chord', t%chord_2, 'm') 298 | call json_value_add( p_prop, 'rotation', t%rotation) 299 | call json_value_add( p_prop, 'root_airfoil', t%af1_text) 300 | call json_value_add( p_prop, 'tip_airfoil', t%af2_text) 301 | call add_int_to_json_obj( p_prop, 'grid', t%nSec, '') 302 | nullify(p_prop) 303 | 304 | !motor structure: 305 | call json_value_create( p_motor) 306 | call to_object( p_motor,'motor') 307 | call json_value_add(p_root, p_motor) 308 | call json_value_add( p_motor, 'name', trim(t%motor%name)) 309 | call add_real_to_json_obj( p_motor, 'kv', t%motor%kv, 'rpm/v') 310 | call add_real_to_json_obj( p_motor, 'no_load_current', t%motor%Io, 'A') 311 | call add_real_to_json_obj( p_motor, 'resistance', t%motor%resistance,'Ohms') 312 | call add_real_to_json_obj( p_motor, 'gear_ratio', t%motor%gearratio, '') 313 | nullify(p_motor) 314 | 315 | !battery structure: 316 | call json_value_create( p_batt) 317 | call to_object( p_batt,'battery') 318 | call json_value_add(p_root, p_batt) 319 | call json_value_add( p_batt, 'name', trim(t%battery%name)) 320 | call add_int_to_json_obj( p_batt, '#cells', t%battery%ncells, '') 321 | call add_real_to_json_obj( p_batt, 'cell_capacity', t%battery%capacity, 'mAh') 322 | call add_real_to_json_obj( p_batt, 'cell_no_load_voltage', t%battery%Eo, 'V') 323 | call add_real_to_json_obj( p_batt, 'cell_impedance', t%battery%resistance,'Ohms') 324 | call add_real_to_json_obj( p_batt, 'c_rating', t%battery%crating, '') 325 | nullify(p_batt) 326 | 327 | !ESC structure: 328 | call json_value_create( p_esc) 329 | call to_object( p_esc,'esc') 330 | call json_value_add(p_root, p_esc) 331 | call json_value_add( p_esc, 'name', trim(t%esc%name)) 332 | call add_real_to_json_obj( p_esc, 'resistance', t%esc%resistance,'Ohms') 333 | nullify(p_esc) 334 | 335 | write(*,'(A)') 'writing file '//trim(filename)//'...' 336 | open(newunit=iunit, file=filename, status='REPLACE') 337 | call json_print(p_root,iunit) 338 | close(iunit) 339 | call json_destroy(p_root) 340 | write(*,'(A)') 'done.' 341 | 342 | end subroutine prop_write_json_file 343 | 344 | !----------------------------------------------------------------------------------------------------------- 345 | subroutine add_real_to_json_obj(me, name, value, units) 346 | implicit none 347 | type(json_value),pointer :: me, var 348 | character(len=*),intent(in) :: name, units 349 | real, intent(in) :: value 350 | nullify(var) 351 | call json_value_create(var) 352 | call to_object(var,name) 353 | call json_value_add(var, 'value', value) 354 | call json_value_add(var, 'units', trim(units)) 355 | call json_value_add(me, var) 356 | nullify(var) 357 | end subroutine add_real_to_json_obj 358 | !----------------------------------------------------------------------------------------------------------- 359 | subroutine add_int_to_json_obj(me, name, value, units) 360 | implicit none 361 | type(json_value),pointer :: me, var 362 | character(len=*),intent(in) :: name, units 363 | integer, intent(in) :: value 364 | nullify(var) 365 | call json_value_create(var) 366 | call to_object(var,name) 367 | call json_value_add(var, 'value', value) 368 | call json_value_add(var, 'units', trim(units)) 369 | call json_value_add(me, var) 370 | nullify(var) 371 | end subroutine add_int_to_json_obj 372 | 373 | !----------------------------------------------------------------------------------------------------------- 374 | subroutine prop_init_setup(t) 375 | type(prop_t) :: t 376 | integer :: isec 377 | 378 | write(*,*) 'Setting up case...' 379 | 380 | call prop_allocate(t) 381 | call prop_setup(t) 382 | call prop_write_attributes(t) 383 | 384 | end subroutine prop_init_setup 385 | 386 | !----------------------------------------------------------------------------------------------------------- 387 | subroutine prop_run_rpm(t,rpm) 388 | type(prop_t) :: t 389 | real :: rpm 390 | run_type = 'rpm' 391 | t%rpm = rpm 392 | write(*,*) 'Target RPM = ',t%rpm 393 | 394 | call prop_solve(t,2) 395 | 396 | end subroutine prop_run_rpm 397 | 398 | !----------------------------------------------------------------------------------------------------------- 399 | subroutine prop_run_throttle(t,throttle) 400 | type(prop_t) :: t 401 | integer :: i 402 | real :: throttle,rpm_error 403 | 404 | run_type = 'throttle' 405 | t%throttle = throttle 406 | rpm_error = 1.0 407 | t%rpm = 10000.0 408 | t%motor%rpm = 0.0 409 | write(*,*) 'Target throttle = ',t%throttle 410 | 411 | ! do i=1,10 412 | write(*,*) 413 | write(*,*) ' Propeller_RPM Motor_RPM Error' 414 | do while (abs(t%rpm-t%motor%rpm*t%motor%gearratio) > rpm_error) 415 | write(*,*) t%rpm,t%motor%rpm*t%motor%gearratio,abs(t%rpm-t%motor%rpm*t%motor%gearratio) 416 | call prop_solve(t,0) 417 | t%rpm = t%rpm - 0.3*(t%rpm - t%motor%rpm*t%motor%gearratio) 418 | end do 419 | call prop_solve(t,2) 420 | end subroutine prop_run_throttle 421 | 422 | !----------------------------------------------------------------------------------------------------------- 423 | subroutine prop_run_thrust(t,thrust) 424 | type(prop_t) :: t 425 | integer :: i 426 | real :: thrust,thrust_error,C0 427 | 428 | run_type = 'thrust' 429 | thrust_error = 0.01 !0.01=1.0% 430 | t%rpm = 10000.0 431 | t%Thrust = 0.0 432 | write(*,*) 'Target thrust = ',thrust 433 | 434 | ! do i=1,10 435 | write(*,*) 436 | write(*,*) ' RPM Thrust [N]' 437 | do while (abs(t%Thrust-thrust)/thrust > thrust_error) 438 | call prop_solve(t,0) 439 | write(*,*) t%rpm,t%Thrust 440 | C0 = t%Thrust/(t%rpm)**2 !assumes quadratic 441 | t%rpm = sqrt(thrust/C0) 442 | end do 443 | call prop_solve(t,2) 444 | end subroutine prop_run_thrust 445 | 446 | !----------------------------------------------------------------------------------------------------------- 447 | subroutine prop_solve(t,iprint) 448 | type(prop_t) :: t 449 | type(section_t),pointer :: si 450 | integer :: isec,iter,iprint 451 | real :: ei,ei_error,x0,x1,guess 452 | real :: time1,time2 453 | ! call cpu_time(time1) 454 | 455 | 456 | ei_error = 0.000001 457 | guess = 0.1 458 | t%omega = t%rpm*2.0*pi/60.0 459 | 460 | do isec=t%nSec,1,-1 !backwards is more stable because each case starts with initial guess of previous. 461 | si => t%sec(isec) 462 | si%einf = atan(t%velocity/(t%omega*si%rc)) 463 | 464 | ei = guess 465 | x0 = guess 466 | x1 = 1.1*guess 467 | iter = 0 468 | ! write(*,*) isec,t%nblades, t%diameter, t%sec(t%nSec)%beta,si%rc,si%chord_c,si%einf,si%beta,si%aL0,prop_function(t,si,ei) 469 | 470 | !Use the secant method to solve for ei 471 | do while (abs(prop_function(t,si,ei)).gt.ei_error) 472 | iter = iter + 1 473 | ei = x1 - prop_function(t,si,x1)*(x1 - x0)/(prop_function(t,si,x1) - prop_function(t,si,x0)) 474 | x0 = x1 475 | x1 = ei 476 | ! write(*,*) iter,ei,prop_function(t,si,ei) 477 | end do 478 | si%ei = ei 479 | guess = ei 480 | ! write(*,*) 'iterations: ',iter 481 | end do 482 | 483 | call prop_forces(t,iprint) 484 | 485 | ! call cpu_time(time2) 486 | ! write(*,*) 'CPU time to solve (sec): ',time2-time1 487 | end subroutine prop_solve 488 | 489 | !----------------------------------------------------------------------------------------------------------- 490 | real function prop_function(t,si,ei) 491 | type(prop_t) :: t 492 | type(section_t),pointer :: si 493 | real :: ei 494 | integer :: k 495 | real :: dp,betat,r,cb,einf,beta,aL0 496 | 497 | k = t%nblades 498 | dp = t%diameter 499 | betat = t%sec(t%nSec)%beta 500 | 501 | r = si%rc 502 | cb = si%chord_c 503 | einf = si%einf 504 | beta = si%beta 505 | aL0 = si%aL0 506 | si%alpha = beta - einf - ei + aL0 507 | 508 | prop_function = real(k)*cb*sec_CL(si)/16.0/r - acos(exp(-k*(1.0-2.0*r/dp)/2.0/sin(betat)))*tan(ei)*sin(einf+ei) 509 | 510 | end function prop_function 511 | 512 | !----------------------------------------------------------------------------------------------------------- 513 | subroutine prop_forces(t,iprint) 514 | type(prop_t) :: t 515 | type(section_t),pointer :: si 516 | integer :: isec,iprint 517 | real :: ei,einf,r,cb,dr,denom 518 | 120 Format(8ES25.13) 519 | 520 | t%Thrust = 0.0 521 | t%Torque = 0.0 522 | t%Power = 0.0 523 | 524 | do isec=1,t%nSec 525 | si => t%sec(isec) 526 | ei = si%ei 527 | einf = si%einf 528 | r = si%rc 529 | cb = si%chord_c 530 | dr = abs(si%r2-si%r1) !left side is backwards 531 | !alpha is already set from the solver 532 | 533 | t%Thrust = t%Thrust + cb* (r*cos(ei)/cos(einf))**2*(sec_CL(si)*cos(einf+ei) - sec_CD(si)*sin(einf+ei))*dr 534 | t%Torque = t%Torque + cb*r*(r*cos(ei)/cos(einf))**2*(sec_CD(si)*cos(einf+ei) + sec_CL(si)*sin(einf+ei))*dr 535 | end do 536 | 537 | t%Thrust = t%Thrust*t%nblades*t%density*t%omega**2/2.0 538 | t%Torque = t%Torque*t%nblades*t%density*t%omega**2/2.0 539 | t%Power = t%Torque*t%omega 540 | 541 | denom = t%density*(t%omega/2.0/pi)**2*t%diameter**4 542 | 543 | t%C_thrust = t%Thrust/denom 544 | t%C_torque = t%Torque/denom/t%diameter 545 | t%C_power = t%Power/denom/(t%omega/2.0/pi)/t%diameter 546 | 547 | if(iprint.gt.0) then 548 | !write forces to the screen 549 | write(*,*) '------------ Propeller Solution ---------------' 550 | write(*,*) ' Thrust(N) Torque(N-m) Power(W) C_Thrust & 551 | &C_Torque C_Power' 552 | write(*,120) t%Thrust,t%Torque,t%Power,t%C_thrust,t%C_torque,t%C_power 553 | end if 554 | 555 | !update motor/battery/prop combo 556 | t%motor%torque = t%Torque/t%motor%gearratio 557 | t%motor%current = pi/30.0*t%motor%kv*t%motor%torque + t%motor%Io 558 | t%battery%voltage = t%battery%Eo * real(t%battery%ncells) - t%motor%current*t%battery%resistance * real(t%battery%ncells) 559 | 560 | if(run_type.eq.'throttle') then 561 | t%battery%current = t%throttle*t%motor%current 562 | t%esc%eta = 1.0-0.078*(1.0-t%throttle) 563 | t%motor%voltage = t%esc%eta*t%throttle*t%battery%voltage - t%motor%current*t%esc%resistance 564 | t%motor%rpm = t%motor%kv*(t%motor%voltage - t%motor%current*t%motor%resistance) 565 | else 566 | t%motor%rpm = t%rpm/t%motor%gearratio 567 | t%motor%voltage = t%motor%rpm/t%motor%kv + t%motor%current*t%motor%resistance 568 | t%throttle = (t%motor%voltage + t%motor%current*t%esc%resistance)/t%battery%voltage 569 | t%esc%eta = 1.0 !Fix This 570 | t%battery%current = t%throttle*t%motor%current 571 | end if 572 | t%motor%brakepower = pi/30.0*t%motor%torque*t%motor%rpm 573 | t%battery%endurance = t%battery%capacity*real(t%battery%ncells)/1000.0/t%battery%current*60.0 574 | t%battery%power = t%battery%current * t%battery%voltage 575 | t%eta = t%motor%brakepower/t%battery%power 576 | 577 | 578 | 579 | if(iprint.gt.1) then 580 | write(*,*) '------------ System Properties --------------' 581 | write(*,*) ' Motor Torque (Nm) = ',t%motor%torque 582 | write(*,*) ' Motor Current (A) = ',t%motor%current 583 | write(*,*) ' Motor Voltage (V) = ',t%motor%voltage 584 | write(*,*) ' Motor RPM = ',t%motor%rpm 585 | write(*,*) ' Motor Brake Power (W) = ',t%motor%brakepower 586 | write(*,*) 587 | write(*,*) ' Battery Current (A) = ',t%battery%current 588 | write(*,*) ' Battery Voltage (V) = ',t%battery%voltage 589 | write(*,*) ' Battery Power (W) = ',t%battery%power 590 | write(*,*) 'Battery Endurance (min) = ',t%battery%endurance 591 | write(*,*) 592 | write(*,*) ' System Efficiency = ',t%eta 593 | write(*,*) ' Throttle = ',t%throttle 594 | write(*,*) ' Thrust (N) = ',t%Thrust 595 | write(*,*) 596 | end if 597 | 598 | end subroutine prop_forces 599 | 600 | !----------------------------------------------------------------------------------------------------------- 601 | subroutine prop_data_dump(t) 602 | type(prop_t) :: t 603 | character(100) :: filename 604 | integer :: isec,ierror 605 | type(section_t),pointer :: si 606 | 120 Format(A15, 10ES25.13) 607 | 608 | filename = trim(adjustl(t%master_filename))//'_dump.txt' 609 | open(unit = 10, File = filename, action = 'write', iostat = ierror) 610 | write(10,*) ' PropName ControlPoint(x) ControlPoint(y) ControlPoint(z) & 611 | &Chord alpha(deg) CL CD & 612 | &Cm area' 613 | do isec=1,t%nSec 614 | si => t%sec(isec) 615 | write(10,120) t%name,si%PC(:),si%chord_c,si%alpha*180.0/pi,sec_CL(si),sec_CD(si),sec_Cm(si),si%ds 616 | end do 617 | close(10) 618 | end subroutine prop_data_dump 619 | 620 | !----------------------------------------------------------------------------------------------------------- 621 | subroutine prop_setup(t) 622 | type(prop_t) :: t 623 | integer :: isec 624 | real :: r1,r2,rc,aL0,lambda,beta,temp 625 | real :: qvec(3),nvec(3),avec(3),start(3),dtheta,percent_1,percent_2,percent_c,chord_1,chord_2,span 626 | real :: my_twist, twist1, twist2 627 | type(dataset_t) :: data_pitch,data_chord 628 | 629 | !allocate arrays from files 630 | if(t%f_chord .ne. 'none') then 631 | call ds_create_from_file(data_chord,t%f_chord,2) 632 | end if 633 | if(t%f_pitch .ne. 'none') then 634 | call ds_create_from_file(data_pitch,t%f_pitch,2) 635 | end if 636 | 637 | t%span = t%diameter/2.0 - t%hubradius 638 | t%root = 0.0 639 | t%root(2) = t%hubradius 640 | if(t%side.eq.'left') t%root(2) = -t%hubradius 641 | start = t%root 642 | 643 | dtheta = pi/real(t%nSec) 644 | t%area = 0.0 645 | span = 0.0 646 | 647 | do isec=1,t%nSec 648 | 649 | percent_1 = 0.5*(1.0-cos(dtheta*real(isec-1))) 650 | percent_2 = 0.5*(1.0-cos(dtheta*real(isec))) 651 | percent_c = 0.5*(1.0-cos(dtheta*(real(isec)-0.5))) 652 | if(t%side.eq.'left') then !must handle differently for opposite motion 653 | temp = percent_1 654 | percent_1 = percent_2 655 | percent_2 = temp 656 | end if 657 | 658 | r1 = t%hubradius + percent_1*(t%span) 659 | r2 = t%hubradius + percent_2*(t%span) 660 | rc = t%hubradius + percent_c*(t%span) 661 | 662 | !data from wing file 663 | if(t%chord_2 >= 0.0) then !linear 664 | chord_1 = t%chord_1 + percent_1*(t%chord_2 - t%chord_1) 665 | chord_2 = t%chord_1 + percent_2*(t%chord_2 - t%chord_1) 666 | else !elliptic wing 667 | chord_1 = t%diameter*0.075*sqrt(1.0-(2.0*r1/t%diameter)**2) 668 | chord_2 = t%diameter*0.075*sqrt(1.0-(2.0*r2/t%diameter)**2) 669 | end if 670 | 671 | aL0 = t%af1%aL0 + percent_c*(t%af2%aL0 - t%af1%aL0) 672 | lambda = 2.0*pi*rc*(t%pitch - 2.0*pi*rc*tan(aL0))/(2.0*pi*rc + t%pitch*tan(aL0)) !aerodynamic pitch (length) 673 | t%sec(isec)%beta = atan(lambda/(2.0*pi*rc)) !aerodynamic pitch angle 674 | my_twist = t%sec(isec)%beta + aL0 !geometric pitch angle 675 | t%sec(isec)%aL0 = aL0 676 | 677 | !For geometry purposes 678 | aL0 = t%af1%aL0 + percent_1*(t%af2%aL0 - t%af1%aL0) 679 | lambda = 2.0*pi*r1*(t%pitch - 2.0*pi*r1*tan(aL0))/(2.0*pi*r1 + t%pitch*tan(aL0)) 680 | beta = atan(lambda/(2.0*pi*r1)) 681 | twist1 = beta + aL0 682 | 683 | aL0 = t%af1%aL0 + percent_2*(t%af2%aL0 - t%af1%aL0) 684 | lambda = 2.0*pi*r2*(t%pitch - 2.0*pi*r2*tan(aL0))/(2.0*pi*r2 + t%pitch*tan(aL0)) 685 | beta = atan(lambda/(2.0*pi*r2)) 686 | twist2 = beta + aL0 687 | 688 | t%sec(isec)%dihedral1 = 0.0 689 | t%sec(isec)%dihedral2 = 0.0 690 | 691 | ! data from input files 692 | if(t%f_chord .ne. 'none') then 693 | chord_1 = t%chord_1 * ds_linear_interpolate_col(data_chord,percent_1,1,2) 694 | chord_2 = t%chord_1 * ds_linear_interpolate_col(data_chord,percent_2,1,2) 695 | end if 696 | if(t%f_pitch .ne. 'none') then !fix this. Need to update t%sec(isec)%beta for twist file input 697 | my_twist = ds_linear_interpolate_col(data_pitch,percent_c,1,2)*pi/180.0 698 | twist1 = ds_linear_interpolate_col(data_pitch,percent_1,1,2)*pi/180.0 699 | twist2 = ds_linear_interpolate_col(data_pitch,percent_2,1,2)*pi/180.0 700 | end if 701 | 702 | !for geometry purposes 703 | t%sec(isec)%twist1 = twist1 704 | t%sec(isec)%twist2 = twist2 705 | 706 | !operate! 707 | qvec(1) = 0.0; qvec(2) = 1.0; qvec(3) = 0.0 708 | nvec(1) = 0.0; nvec(2) = 0.0; nvec(3) =-1.0 709 | avec(1) =-1.0; avec(2) = 0.0; avec(3) = 0.0 710 | 711 | call math_rot_y(nvec,my_twist) 712 | call math_rot_y(avec,my_twist) 713 | if(t%side.eq.'left') then !must handle differently for opposite motion 714 | qvec(2) = -qvec(2) 715 | nvec(2) = -nvec(2) 716 | avec(2) = -avec(2) 717 | end if 718 | 719 | t%sec(isec)%r1 = r1 720 | t%sec(isec)%r2 = r2 721 | t%sec(isec)%rc = rc 722 | t%sec(isec)%percent_1 = percent_1 723 | t%sec(isec)%percent_2 = percent_2 724 | t%sec(isec)%percent_c = percent_c 725 | t%sec(isec)%chord_1 = chord_1 726 | t%sec(isec)%chord_2 = chord_2 727 | t%sec(isec)%chord_c = 2.0/3.0*(chord_1**2 + chord_1*chord_2 + chord_2**2)/(chord_1 + chord_2) 728 | t%sec(isec)%ds = abs(0.5*(chord_1+chord_2)*(percent_2-percent_1)*t%span) 729 | t%sec(isec)%un(:) = nvec(:) 730 | t%sec(isec)%ua(:) = avec(:) 731 | call math_cross_product(avec(:),nvec(:),t%sec(isec)%us) 732 | 733 | if(t%side.eq.'left') then !must handle differently for opposite motion 734 | t%sec(isec)%P2(:) = start(:) 735 | t%sec(isec)%P1(:) = start(:) + qvec(:)*(percent_1-percent_2)*t%span 736 | else 737 | t%sec(isec)%P1(:) = start(:) 738 | t%sec(isec)%P2(:) = start(:) + qvec(:)*(percent_2-percent_1)*t%span 739 | end if 740 | 741 | t%sec(isec)%dl(:) = t%sec(isec)%P2(:) - t%sec(isec)%P1(:) 742 | t%sec(isec)%PC(:) = t%sec(isec)%P1(:) + t%sec(isec)%dl(:)*(percent_c - percent_1)/(percent_2 - percent_1) 743 | t%sec(isec)%zeta(:) = t%sec(isec)%chord_c*t%sec(isec)%dl(:)/t%sec(isec)%ds 744 | t%sec(isec)%Rroot(:) = t%sec(isec)%PC(:) - t%root(:) 745 | t%sec(isec)%af1 => t%af1 746 | t%sec(isec)%af2 => t%af2 747 | 748 | !adjust start point 749 | if(t%side.eq.'left') then !must handle differently for opposite motion 750 | start(:) = t%sec(isec)%P1(:) 751 | else 752 | start(:) = t%sec(isec)%P2(:) 753 | end if 754 | 755 | span = span + sqrt( (t%sec(isec)%P2(2)-t%sec(isec)%P1(2))**2 + (t%sec(isec)%P2(3)-t%sec(isec)%P1(3))**2) 756 | t%area = t%area + t%sec(isec)%ds 757 | 758 | end do 759 | t%tip(:) = start(:) 760 | ! write(*,*) 'span: ',span 761 | ! write(*,*) 't%area : ',t%area 762 | end subroutine prop_setup 763 | 764 | !----------------------------------------------------------------------------------------------------------- 765 | subroutine prop_write_attributes(t) 766 | type(prop_t) :: t 767 | 768 | write(*,*) '-------------------------------------------------------------' 769 | write(*,*) ' Propeller name : ',t%name 770 | write(*,*) ' Number of blades : ',t%nblades 771 | write(*,*) ' Diameter (m) : [',t%diameter,']' 772 | write(*,*) ' Pitch (m) : [',t%pitch,']' 773 | write(*,*) ' Hub Radius (m) : [',t%hubradius,']' 774 | write(*,*) ' Root Chord (m) : [',t%chord_1,']' 775 | write(*,*) ' Tip Chord (m) : [',t%chord_2,']' 776 | write(*,*) ' Rotation : [',t%rotation,']' 777 | write(*,*) ' Root Airfoil : [',trim(t%af1_text),']' 778 | write(*,*) ' Tip Airfoil : [',trim(t%af2_text),']' 779 | write(*,*) ' Grid Points : [',t%nSec,']' 780 | write(*,*) 781 | 782 | end subroutine prop_write_attributes 783 | 784 | end module prop_m 785 | --------------------------------------------------------------------------------