├── AndroidManifest.xml ├── Btn_info.pde ├── Button_class.pde ├── Dialog_class.pde ├── Dpad.pde ├── Dugme_class.pde ├── FFBgraph_class.pde ├── Graph_class.pde ├── Info_class.pde ├── NumboxIn_class.pde ├── Profile_class.pde ├── README.md ├── Slajder_class.pde ├── Wheel_class.pde ├── XYshifter.pde ├── code └── sketch.properties ├── data ├── Arduino Leonardo wheel v3 ├── Arduino Leonardo wheel v4 ├── Arduino Leonardo wheel v5 ├── COM_cfg.txt ├── TX_wheel_rim_small_alpha.png ├── Wheel_control_gamecontrols_setup.png ├── Wheel_control_step0.png ├── Wheel_control_step1.png ├── Wheel_control_step2.png ├── Wheel_control_step2a.png ├── Wheel_control_step3.png ├── Wheel_control_v2_6_3.png ├── Wheel_control_v2_6_3_dac_as5600_2ffb_axis.png ├── Wheel_control_v2_6_3_pwm_as5600_2ffb_axis.png ├── arduino leonardo ffb wheel.jpg ├── axisColor_cfg.txt ├── guiChangeLog.txt ├── manual.txt ├── processing_3_5_4_status_log.txt ├── profile1.txt ├── profile2.txt ├── profile3.txt ├── profile4.txt ├── rane_wheel_rim_D-shape.png ├── rane_wheel_rim_O-shape.png └── setupTextLog.txt ├── wheel_control.pde ├── wheel_control_v2_6_3.zip └── xycals.pde /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Btn_info.pde: -------------------------------------------------------------------------------- 1 | class InfoButton { 2 | int ns, as, dp; 3 | float x, y, sx, sy; 4 | String[] n = new String[ns]; 5 | String d; 6 | Boolean enabled, hiden, showDescription; 7 | 8 | InfoButton(float posx, float posy, float sizex, float sizey, int nSegments, String[] name, String description, int descriptionPos) { 9 | x = posx; 10 | y = posy; 11 | sx = sizex; 12 | sy = sizey; 13 | ns = nSegments; 14 | n = name; 15 | as = -1; // no segment is active 16 | d = description; 17 | dp = descriptionPos; 18 | enabled = false; 19 | hiden = true; 20 | showDescription = false; 21 | } 22 | 23 | void update() { 24 | if (mouseX >= x && mouseX <= x+sx && mouseY >= y && mouseY <= y+sy) { 25 | showDescription = true; // if howered with mouse 26 | } else { 27 | showDescription = false; // if not howered 28 | } 29 | } 30 | void show() { 31 | color ce = color (0, 200, 150); // red 32 | color cd = color (0, 0, 100); // gray 33 | thue = 255; // white 34 | if (!hiden) { 35 | for (int i=0; i= x && mouseX <= x+sx && mouseY >= y && mouseY <= y+sy) { 25 | controlb[i] = true; 26 | } else { 27 | controlb[i] = false; 28 | } 29 | if (controlb[i] && mousePressed || buttonpressed[i] ) { // green with black text (activated) 30 | col[0] = 96; 31 | col[1] = 200; 32 | col[2] = 150; 33 | thue = 0; 34 | showInfo = false; 35 | } else if (controlb[i] && !mousePressed) { // yellow with white text (howered) 36 | col[0] = 40; 37 | col[1] = 200; 38 | col[2] = 180; 39 | thue = 255; 40 | showInfo = true; 41 | } else if (!controlb[i]) { // red with white text (deactivated) 42 | col[0] = 0; 43 | col[1] = 200; 44 | col[2] = 150; 45 | thue = 255; 46 | showInfo = false; 47 | } 48 | } else { // gray with white text (not enabled) 49 | col[0] = 0; 50 | col[1] = 0; 51 | col[2] = 100; 52 | thue = 255; 53 | showInfo = false; 54 | } 55 | } 56 | 57 | void show() { 58 | fill(col[0], col[1], col[2]); 59 | strokeWeight(1); 60 | stroke(255); 61 | rect(x, y, sx, sy); 62 | pushMatrix(); 63 | textSize(font_size); 64 | fill(thue); 65 | text(t, x+font_size*0.5, y+font_size); 66 | popMatrix(); 67 | if (showInfo) { 68 | pushMatrix(); 69 | if (dp == 0) { 70 | translate(x, y-1.15*sy); // put description above 71 | } else if (dp == 1) { 72 | translate(x, y+1.15*sy); // put description bellow 73 | } else if (dp == 2) { 74 | translate(x-textWidth(d)-font_size, y); // put description to the left side 75 | } else if (dp == 3) { 76 | translate(x+sx, y); // put description to the right side 77 | } 78 | noFill(); 79 | rect(0, 0, textWidth(d)+font_size, sy); 80 | text(d, font_size*0.5, font_size); 81 | popMatrix(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Dialog_class.pde: -------------------------------------------------------------------------------- 1 | class Dialog { 2 | float x; 3 | float y; 4 | float s; 5 | String t; 6 | float l; 7 | 8 | Dialog(float posx, float posy, float size, String text) { 9 | x = posx; 10 | y = posy; 11 | s = size; 12 | t = text; 13 | } 14 | 15 | void update(String newText) { 16 | t=newText; 17 | //l=newText.length(); 18 | l=textWidth(newText) + font_size; 19 | } 20 | 21 | void show() { 22 | fill(148, 200, 100); 23 | strokeWeight(1); 24 | stroke(255); 25 | //rect(x, y, (l+4)*font_size/2, font_size*1.5); 26 | rect(x, y, l, s*1.2); 27 | pushMatrix(); 28 | textSize(font_size); 29 | fill(255); 30 | translate(x, y); 31 | text(t, font_size*0.15+2, font_size*1.1); 32 | popMatrix(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Dpad.pde: -------------------------------------------------------------------------------- 1 | class HatSW { 2 | float x; 3 | float y; 4 | float r; 5 | float R; 6 | boolean enabled; 7 | 8 | HatSW (float posx, float posy, float radius, float Radius) { 9 | x = posx; 10 | y = posy; 11 | r = radius; 12 | R = Radius; 13 | enabled = false; 14 | } 15 | 16 | void update() { 17 | if (gpad.getButton("Hat").pressed()) { 18 | hatvalue = floor(gpad.getButton("Hat").getValue()); 19 | } 20 | } 21 | 22 | void show() { 23 | int hue; 24 | if (gpad.getButton("Hat").pressed()) { 25 | hue = 64; 26 | } else { 27 | hue = 0; 28 | } 29 | stroke(255); 30 | strokeWeight(1); 31 | if (enabled) { 32 | fill(hue, 255, 255); 33 | } else { 34 | fill(0, 0, 100); 35 | } 36 | ellipse(x, y, r, r); 37 | noFill(); 38 | stroke(255, 200); 39 | ellipse(x, y, R, R); 40 | } 41 | 42 | void showArrow() { 43 | if (gpad.getButton("Hat").pressed()) { 44 | pushMatrix(); 45 | translate(x, y); 46 | rotate(TWO_PI*hatvalue/8.0+(1.0/2.0)*PI); 47 | beginShape(); 48 | noStroke(); 49 | fill(64, 255, 255); 50 | int hg = floor(R*0.4); 51 | vertex(-4, hg+0); 52 | vertex(4, hg+0); 53 | vertex(4, hg+5); 54 | vertex(6, hg+5); 55 | vertex(0, hg+12); 56 | vertex(-6, hg+5); 57 | vertex(-4, hg+5); 58 | endShape(); 59 | popMatrix(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Dugme_class.pde: -------------------------------------------------------------------------------- 1 | class Dugme { 2 | float x; 3 | float y; 4 | float s; 5 | boolean enabled; 6 | 7 | Dugme(float posx, float posy, float size) { 8 | x = posx; 9 | y = posy; 10 | s = size; 11 | enabled = false; 12 | } 13 | 14 | void update() { 15 | if (gpad.getButton("0").pressed()) { 16 | Button[0] = true; 17 | } else { 18 | Button[0] = false; 19 | } 20 | if (gpad.getButton("1").pressed()) { 21 | Button[1] = true; 22 | } else { 23 | Button[1] = false; 24 | } 25 | if (gpad.getButton("2").pressed()) { 26 | Button[2] = true; 27 | } else { 28 | Button[2] = false; 29 | } 30 | if (gpad.getButton("3").pressed()) { 31 | Button[3] = true; 32 | } else { 33 | Button[3] = false; 34 | } 35 | if (gpad.getButton("4").pressed()) { 36 | Button[4] = true; 37 | } else { 38 | Button[4] = false; 39 | } 40 | if (gpad.getButton("5").pressed()) { 41 | Button[5] = true; 42 | } else { 43 | Button[5] = false; 44 | } 45 | if (gpad.getButton("6").pressed()) { 46 | Button[6] = true; 47 | } else { 48 | Button[6] = false; 49 | } 50 | if (gpad.getButton("7").pressed()) { 51 | Button[7] = true; 52 | } else { 53 | Button[7] = false; 54 | } 55 | if (gpad.getButton("8").pressed()) { 56 | Button[8] = true; 57 | } else { 58 | Button[8] = false; 59 | } 60 | if (gpad.getButton("9").pressed()) { 61 | Button[9] = true; 62 | } else { 63 | Button[9] = false; 64 | } 65 | if (gpad.getButton("10").pressed()) { 66 | Button[10] = true; 67 | } else { 68 | Button[10] = false; 69 | } 70 | if (gpad.getButton("11").pressed()) { 71 | Button[11] = true; 72 | } else { 73 | Button[11] = false; 74 | } 75 | if (gpad.getButton("12").pressed()) { 76 | Button[12] = true; 77 | } else { 78 | Button[12] = false; 79 | } 80 | if (gpad.getButton("13").pressed()) { 81 | Button[13] = true; 82 | } else { 83 | Button[13] = false; 84 | } 85 | if (gpad.getButton("14").pressed()) { 86 | Button[14] = true; 87 | } else { 88 | Button[14] = false; 89 | } 90 | if (gpad.getButton("15").pressed()) { 91 | Button[15] = true; 92 | } else { 93 | Button[15] = false; 94 | } 95 | if (gpad.getButton("16").pressed()) { 96 | Button[16] = true; 97 | } else { 98 | Button[16] = false; 99 | } 100 | if (gpad.getButton("17").pressed()) { 101 | Button[17] = true; 102 | } else { 103 | Button[17] = false; 104 | } 105 | if (gpad.getButton("18").pressed()) { 106 | Button[18] = true; 107 | } else { 108 | Button[18] = false; 109 | } 110 | if (gpad.getButton("19").pressed()) { 111 | Button[19] = true; 112 | } else { 113 | Button[19] = false; 114 | } 115 | if (gpad.getButton("20").pressed()) { 116 | Button[20] = true; 117 | } else { 118 | Button[20] = false; 119 | } 120 | if (gpad.getButton("21").pressed()) { 121 | Button[21] = true; 122 | } else { 123 | Button[21] = false; 124 | } 125 | if (gpad.getButton("22").pressed()) { 126 | Button[22] = true; 127 | } else { 128 | Button[22] = false; 129 | } 130 | if (gpad.getButton("23").pressed()) { 131 | Button[23] = true; 132 | } else { 133 | Button[23] = false; 134 | } 135 | /*if (gpad.getButton("24").pressed()) { 136 | Button[24] = true; 137 | } else { 138 | Button[24] = false; 139 | } 140 | if (gpad.getButton("25").pressed()) { 141 | Button[25] = true; 142 | } else { 143 | Button[25] = false; 144 | } 145 | if (gpad.getButton("26").pressed()) { 146 | Button[26] = true; 147 | } else { 148 | Button[26] = false; 149 | } 150 | if (gpad.getButton("27").pressed()) { 151 | Button[27] = true; 152 | } else { 153 | Button[27] = false; 154 | } 155 | if (gpad.getButton("28").pressed()) { 156 | Button[28] = true; 157 | } else { 158 | Button[28] = false; 159 | } 160 | if (gpad.getButton("29").pressed()) { 161 | Button[29] = true; 162 | } else { 163 | Button[29] = false; 164 | } 165 | if (gpad.getButton("30").pressed()) { 166 | Button[30] = true; 167 | } else { 168 | Button[30] = false; 169 | }*/ 170 | } 171 | 172 | void show(int i) { 173 | if (dActByp) enabled = true; // bypass the button in-activation 174 | int hue; 175 | if (buttonValue) { 176 | hue = 64; 177 | } else { 178 | hue = 0; 179 | } 180 | if (enabled) { 181 | fill(hue, 255, 255); // red 182 | } else { 183 | fill(0, 0, 100); // gray 184 | } 185 | strokeWeight(1); 186 | stroke(255); 187 | rect(x, y, s, s); 188 | pushMatrix(); 189 | textSize(font_size); 190 | if (enabled) { 191 | fill(0); // black text when activated 192 | } else { 193 | fill(235); // white-ish text when de-activated 194 | } 195 | if (i<=9) { 196 | text(i, x+font_size/2, y+font_size*1.2); 197 | } else { 198 | text(i, x+font_size*0.15, y+font_size*1.2); 199 | } 200 | popMatrix(); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /FFBgraph_class.pde: -------------------------------------------------------------------------------- 1 | class FFBgraph { 2 | float x, y; 3 | int ps; 4 | int[] pointY = new int [gbuffer]; 5 | float gwidthX, gh, sclX, sclY; 6 | 7 | FFBgraph(float posx, float posy, float gheight, int pointsize) { 8 | x = posx - 1; 9 | y = posy - 1; 10 | gh = gheight - 1; 11 | ps = pointsize; 12 | gwidthX = gbuffer / gskip; 13 | sclX = gwidthX / gbuffer; 14 | sclY = gh / (2*maxTorque); 15 | } 16 | 17 | void update(String val1) { 18 | pointY[0] = parseInt(val1); 19 | for (int i=pointY.length-1; i>0; i--) { 20 | pointY[i] = pointY[i-1]; 21 | } 22 | } 23 | 24 | void show(int i) { 25 | String gT = "FFB"; 26 | String gL = "left"; 27 | String gR = "right"; 28 | pushMatrix(); 29 | translate(x, y); 30 | noFill(); 31 | strokeWeight(1); 32 | stroke(255); 33 | noFill(); 34 | strokeWeight(1); 35 | stroke(255); 36 | //if (twoFFBaxis_enabled) { 37 | if (i == 0) { // for X-axis 38 | gT = "x" + gT; 39 | } else { // for Y-axis 40 | gT = "y" + gT; 41 | gL = "down"; 42 | gR = "up"; 43 | } 44 | textAlign(RIGHT); 45 | text(pointY[0], 0.25*font_size-gh/2, font_size); // X-axis value (horizontal orientation) 46 | text(gR, -font_size*0.4, font_size); 47 | textAlign(LEFT); 48 | text(gT, gT.length()*font_size-gh/2, font_size); 49 | text(gL, gL.length()*font_size*0.1 - gh, font_size); 50 | //} 51 | rotate(PI/2.0); // rotate CW by 90deg 52 | rectMode(CORNER); 53 | rect(0, 0, gwidthX, gh); // graph frame 54 | if (!twoFFBaxis_enabled) { 55 | //text(pointY[0], (-str(pointY[0]).length()*0.59-1.3)*font_size, gh/2+0.3*font_size); // ffb axis value (vertical orientation), at center of graph 56 | } 57 | //text(-maxTorque, -60, gh-5+0.3*font_size); // min ffb value indicator 58 | //text(maxTorque, -50, 5+0.3*font_size); // max ffb value indicator 59 | pushMatrix(); 60 | translate(0, gh); 61 | int majl = 8; // major tick length 62 | int minl = 4; // minor tick length 63 | if (twoFFBaxis_enabled) { // shorten ticks when we display 2 FFB monitor graphs on top of each other 64 | majl = 5; 65 | minl = 3; 66 | } 67 | int l = 32; // num of major ticks 68 | int p = 5; // num of minor ticks between each major tick 69 | float n = gh/float(l); // major tick pos 70 | float m = n/float(p); // minor tick pos 71 | for (int j = 0; j >= -l; j--) { // draw l+1 major ticks 72 | for (int k = 0; k > -p; k--) { 73 | int f = 1; 74 | if (j < -l/2) f = 0; // for some reason ticks after this one are shifted down by 1 pixel, this is a brute force fix 75 | line(f, n*j, f-majl, n*j); // major ticks 76 | if (j > -l) { // only draw them before last major tick 77 | if (k != 0) { // do not draw minor tick on top of major tick 78 | int t = 0; 79 | if (j == -l/2 && k < -p/2) t = -1; 80 | line(f+t, m*k + n*j, f+t-minl, m*k + n*j); // small ticks 81 | } 82 | } 83 | } 84 | } 85 | popMatrix(); 86 | for (int a=0; a -20; i--) { 69 | for (int j = 0; j > -5; j--) { 70 | line(0, i*n, 10, i*n); 71 | line(0, j*m + i*n, 5, j*m + i*n); 72 | } 73 | } 74 | line(0, -20*n, 10, -20*n); 75 | fill(255); 76 | pushMatrix(); 77 | translate(0, -axisScale); 78 | rotate(-PI/2); 79 | text(value, -4, 25); 80 | text(axisValue, axisScale-50, 25); 81 | popMatrix(); 82 | popMatrix(); 83 | 84 | // graph X axis arrow 85 | pushMatrix(); 86 | beginShape(); 87 | translate(x, y+axisScale); // center of ruler axisScale 88 | //rotate(-PI/2); 89 | translate(level*2, 2); // moving along the ruler 90 | int hg = 0; 91 | fill(hg, 255, 255); 92 | strokeWeight(1); 93 | stroke(255); 94 | vertex(-4, hg+0); 95 | vertex(4, hg+0); 96 | vertex(4, hg+5); 97 | vertex(6, hg+5); 98 | vertex(0, hg+12); 99 | vertex(-6, hg+5); 100 | vertex(-4, hg+5); 101 | endShape(); 102 | popMatrix(); 103 | /*print(value); 104 | print(" "); 105 | print(a);*/ 106 | 107 | // graph Y axis 108 | pushMatrix(); 109 | axisValue = correct_axis(Axis[0]); 110 | level = axisValue/real_wheelTurn*axisScale; 111 | translate(x+axisScale+17, y+axisScale); 112 | //float n = axisScale/10; 113 | //float m = n/5; 114 | for (int i = 0; i > -20; i--) { 115 | for (int j = 0; j > -5; j--) { 116 | line(0, i*n, 10, i*n); 117 | line(0, j*m + i*n, 5, j*m + i*n); 118 | } 119 | } 120 | line(0, -20*n, 10, -20*n); 121 | fill(255); 122 | //text(value, 0, -(2*n+1)*10); 123 | //text(axisValue, 0, 20); 124 | text(axisValue, 20, -(2*n)*10+4); 125 | float minlock = -axisScale+float(lfs_car_wheelTurn)/float(real_wheelTurn)*axisScale; 126 | float maxlock = -minlock -axisScale*2; 127 | strokeWeight(3); 128 | stroke(128, 255, 255); 129 | line(14, minlock, 20, minlock); // min lock limit (red) 130 | line(14, maxlock, 20, maxlock); // max lock limit (green) 131 | strokeWeight(1); 132 | popMatrix(); 133 | 134 | // graph Y axis arrow 135 | pushMatrix(); 136 | beginShape(); 137 | translate(x+axisScale/2+2, y); // center of ruler axisScale 138 | rotate(-PI/2); 139 | translate(level*2, 0); // moving along the ruler 140 | hg = 128; 141 | fill(hg, 255, 255); 142 | strokeWeight(1); 143 | stroke(255); 144 | vertex(-4, hg+0); 145 | vertex(4, hg+0); 146 | vertex(4, hg+5); 147 | vertex(6, hg+5); 148 | vertex(0, hg+12); 149 | vertex(-6, hg+5); 150 | vertex(-4, hg+5); 151 | endShape(); 152 | popMatrix(); 153 | /*print(value); 154 | print(" "); 155 | print(a);*/ 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Info_class.pde: -------------------------------------------------------------------------------- 1 | class Info { 2 | float x; 3 | float y; 4 | int s; 5 | String txt; 6 | String btn; 7 | String fullinfo; 8 | 9 | Info(float posx, float posy, int size, String text, String button) { 10 | x = posx; 11 | y = posy; 12 | s = size; 13 | txt = text; 14 | btn = button; 15 | fullinfo = btn + " : " + txt; 16 | } 17 | 18 | void show(boolean enable) { 19 | float textLength = 0; 20 | textLength = textWidth(fullinfo) + s; 21 | if (enable) { 22 | noFill(); 23 | strokeWeight(1); 24 | stroke(255); 25 | //rect(x, y, s/2*(fullinfo.length()+3), s*1.2); 26 | rect(x, y, textLength, s*1.2); 27 | pushMatrix(); 28 | textSize(font_size); 29 | fill(255); 30 | translate(x, y); 31 | text(fullinfo, font_size/2, font_size-1); 32 | popMatrix(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /NumboxIn_class.pde: -------------------------------------------------------------------------------- 1 | // input handler for a Numberbox that allows the user to 2 | // key in numbers with the keyboard to change the value of the numberbox 3 | public class NumberboxInput { 4 | String text = ""; 5 | Numberbox n; 6 | boolean active; 7 | 8 | NumberboxInput(Numberbox theNumberbox) { 9 | n = theNumberbox; 10 | registerMethod("keyEvent", this); 11 | } 12 | 13 | public void keyEvent(KeyEvent k) { 14 | // only process key event if input is active 15 | if (k.getAction()==KeyEvent.PRESS && active) { 16 | if (k.getKey()=='\n') { // confirm input with enter 17 | submit(); 18 | return; 19 | } else if (k.getKeyCode()==BACKSPACE) { 20 | text = text.isEmpty() ? "":text.substring(0, text.length()-1); 21 | //text = ""; // clear all text with backspace 22 | } else if (k.getKey()<255) { 23 | // check if the input is a valid (decimal) number 24 | //final String regex = "\\d+([.]\\d{0,2})?"; 25 | // check if the input is a 5 digit decimal number 26 | final String regex = "\\d{1,5}?"; 27 | String s = text + k.getKey(); 28 | if (java.util.regex.Pattern.matches(regex, s) ) { 29 | text += k.getKey(); 30 | } 31 | } 32 | n.getValueLabel().setText(this.text); 33 | } 34 | } 35 | 36 | public void setActive(boolean b) { 37 | active = b; 38 | if (active) { 39 | n.getValueLabel().setText(""); 40 | text = ""; 41 | } 42 | } 43 | 44 | public void submit() { 45 | if (!text.isEmpty()) { 46 | n.setValue(int(text)); 47 | text = ""; 48 | } else { 49 | n.getValueLabel().setText(""+int(n.getValue())); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Profile_class.pde: -------------------------------------------------------------------------------- 1 | class Profile { 2 | String name; // profile name 3 | float[] parm; // ffb setting parameter 4 | int[] pMin = new int[num_axis-1]; // pedal min calibration limit 5 | int[] pMax = new int[num_axis-1]; // pedal max calibration limit 6 | int[] xyshCfg = new int[6]; // xy shifter calibration and config 7 | String pedalCalCfg, shifterCalCfg; // pedals and shifter calibration data packed into a string 8 | String[] contents = new String[num_prfset+1]; // we keep settings in each line of profile txt file, where 1st is profile name 9 | 10 | Profile(String n, float[] p, String pcfg, String scfg) { 11 | this.name = n; 12 | this.parm = p; 13 | this.pedalCalCfg = pcfg; 14 | this.shifterCalCfg = scfg; 15 | this.toContents(); 16 | } 17 | 18 | void upload() { // upload last FFB settings from GUI to a profile 19 | for (int i=0; i 24 | 25 | ## Troubleshooting - first time run 26 | 27 | The program will look for all virtual COM port devices plugged into your PC, but it doesn't know to which COM port your Arduino is assigned to. You can either follow the first-time run setup process and select your Arduino COM port from there, or if you experience "stuck in black screen issue" you can do it manually. Locate the data folder and create a txt file named COM_cfg.txt (do not put .txt in the file name, it is just the file extension). Open this file and type COMx, where x is the 1-digit number of the COM port where your Arduino is located. You can find it in the device manager under ports, an example is COM5. Save the file and close it, then try to run wheel control exe again as administrator. If the problem still persists, you may try to install Processing IDE 3.5.4, download missing libraries, and try to run it as a source code from there. Note that you must place all *.pde files from source folder into a folder named wheel_control. 28 | -------------------------------------------------------------------------------- /Slajder_class.pde: -------------------------------------------------------------------------------- 1 | class Slajder { 2 | color c; 3 | float x, y, s, am, axisVal; 4 | String l, la, lb; 5 | xyCal[] yLimits = new xyCal[2]; 6 | boolean yLimitsVisible, xffb, yffb, inactive; 7 | float[] pCal = new float[2]; 8 | int id; 9 | 10 | Slajder(color acolor, float posx, float posy, float size, float axisMax, String label, String labela, String labelb, boolean xffbaxis, boolean yffbaxis) { 11 | c = acolor; 12 | x = posx; 13 | y = posy; 14 | s = size; 15 | am = axisMax; 16 | l = label; 17 | la = labela; 18 | lb = labelb; 19 | xffb = xffbaxis; 20 | yffb = yffbaxis; 21 | yLimits[0] = new xyCal(x-2.8*s, y, s, s, 3, la); 22 | yLimits[1] = new xyCal(x-2.8*s, y-2*axisScale, s, s, 3, lb); 23 | yLimits[0].active = true; 24 | yLimits[1].active = true; 25 | yLimitsVisible = false; 26 | pCal[0] = 0; 27 | pCal[1] = am; 28 | yLimits[0].y = convertToyLimits(0); 29 | yLimits[1].y = convertToyLimits(1); 30 | } 31 | 32 | void update(int i) { 33 | if (i == 0) { // X-axis 34 | axisVal = gpad.getSlider("Xaxis").getValue(); 35 | if (axisVal != prevaxis) wheelMoved = true; 36 | } else if (i == 1) { // Y-axis 37 | axisVal = gpad.getSlider("Yaxis").getValue(); 38 | } else if (i == 2) { // Z-axis 39 | axisVal = gpad.getSlider("Zaxis").getValue(); 40 | } else if (i == 3) { // RX-axis 41 | axisVal = gpad.getSlider("RXaxis").getValue(); 42 | } else if (i == 4) { // RY-axis 43 | axisVal = gpad.getSlider("RYaxis").getValue(); 44 | } else { 45 | axisVal = 0.0; 46 | } 47 | // update axis view with xFFB marking (for now only x is configurable) 48 | if (xFFBAxisIndex == i) { 49 | xffb = true; // axis with xFFB mark 50 | } else { 51 | xffb = false; // axis without xFFB mark 52 | } 53 | // update axis view with yFFB marking (not configurable for now) 54 | if (yFFBAxisIndex == i) { 55 | yffb = true; // axis with yFFB mark 56 | } else { 57 | yffb = false; // axis without yFFB mark 58 | } 59 | setpCal(); 60 | id = i; 61 | } 62 | 63 | void getyLimits(int i) { // get yLimits value from mouse position 64 | yLimits[i].y = mouseY; // update yLimits y position 65 | updateyLimitsLimit(i); 66 | checkyLimitsLimit(i); 67 | } 68 | 69 | void setpCal() { // set pedal calibration from wheel control 70 | for (int i=0; i yLimits[i].limits[3]) { 111 | yLimits[i].y = yLimits[i].limits[3]; 112 | } 113 | } 114 | 115 | void show() { 116 | if (inactive) { 117 | c = color(0, 0, 100); // inactive gray 118 | } 119 | fill(c); 120 | strokeWeight(1); 121 | stroke(255); 122 | pushMatrix(); 123 | rectMode(CORNER); 124 | rect(x, y, s, -axisScale-axisVal*axisScale); 125 | translate(x-15, y); 126 | float n = axisScale/10; 127 | float m = n/5; 128 | for (int i = 0; i > -20; i--) { 129 | for (int j = 0; j > -5; j--) { 130 | line(0, i*n, 10, i*n); 131 | line(0, j*m + i*n, 5, j*m + i*n); 132 | } 133 | } 134 | line(0, -20*n, 10, -20*n); 135 | fill(255); 136 | text(round(map(axisVal, -1, 1, 0, am)), 0, -(2*n+1)*10); 137 | noFill(); 138 | textSize(0.8*font_size); 139 | if (xffb && yffb) { 140 | rect(15, 14, 28, -10); 141 | if (twoFFBaxis_enabled) { 142 | text("xyFFB", 15, 13); 143 | } else { 144 | text("xFFB", 15, 13); 145 | } 146 | } else if (xffb) { 147 | rect(15, 14, 23, -10); 148 | text("xFFB", 15, 13); 149 | } else if (yffb) { 150 | if (twoFFBaxis_enabled) { 151 | text("yFFB", 15, 13); 152 | rect(15, 14, 23, -10); 153 | } 154 | } 155 | fill(255); 156 | textSize(font_size); 157 | if (id < 3) { 158 | text(l, -2, 20); 159 | } else { 160 | text(l, -font_size/2, 20); 161 | } 162 | popMatrix(); 163 | if (yLimitsVisible) { 164 | for (int j=0; j 0.0) { // positive values 133 | if (inp < 10.0) { //0-9.9 134 | out = str(inp).substring(0, 4); 135 | } else { // >=10 136 | if (len > 4) { 137 | out = str(inp).substring(0, 5); 138 | } else { 139 | out = str(inp)+"0"; 140 | } 141 | } 142 | } else { // negative values 143 | if (inp < -10.0) { 144 | if (len > 5) { 145 | out = str(inp).substring(0, 6); 146 | } else { 147 | out = str(inp)+"0"; 148 | } 149 | } else { // >=-10 150 | out = str(inp).substring(0, 5); 151 | } 152 | } 153 | return out; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /XYshifter.pde: -------------------------------------------------------------------------------- 1 | class XYshifter { 2 | float x, y, s, sd, dx, dy, lx, ly; 3 | float shX, shY; 4 | float sCal[] = new float[5]; 5 | byte sConfig; 6 | int gear; 7 | int revGearBit; 8 | xyCal[] xycals = new xyCal[5]; 9 | 10 | XYshifter(float posx, float posy, float scale) { 11 | x = posx; 12 | y = posy; 13 | s = scale; 14 | dx = s*1023.0; 15 | dy = s*1023.0; 16 | lx = dx/25.5; 17 | ly = dy/25.5; 18 | sd = lx; 19 | revGearBit = 0; 20 | xycals[0] = new xyCal(x, y-ly-2, lx, ly, 0, "a"); 21 | xycals[1] = new xyCal(x, y+ly+2+dy, lx, ly, 2, "b"); 22 | xycals[2] = new xyCal(x, y-ly-2, lx, ly, 0, "c"); 23 | xycals[3] = new xyCal(x+dx+lx+2, y, lx, ly, 1, "d"); 24 | xycals[4] = new xyCal(x+0*dx-lx-2, y, lx, ly, 3, "e"); 25 | } 26 | void updatePos() { // update shifter xy position form arduino read buffer string 27 | int[] temp = int(split(rb, ' ')); 28 | shX = temp[0]; 29 | shY = temp[1]; 30 | } 31 | void updateCal(String calibration) { // update shifter calibration form arduino string 32 | float[] temp = float(split(calibration, ' ')); 33 | for (int i=0; i= xycals[3].y) { // 2nd gear 91 | g = 2; 92 | if (showGear) { 93 | rect(1, -2, xycals[0].x-x-2, xycals[3].y-y-dy+4); 94 | fill(255); 95 | translate(-font_size/3, font_size/3); 96 | text(2, (xycals[0].x-x)/2, (xycals[3].y-y-dy)/2); 97 | } 98 | } else if (sx >= xycals[0].x && sx < xycals[1].x && sy < xycals[4].y) { // 3rd gear 99 | g = 3; 100 | if (showGear) { 101 | rect(xycals[0].x-x+2, 1-dy, xycals[1].x-xycals[0].x-3, xycals[4].y-y-2); 102 | fill(255); 103 | translate(-font_size/3, font_size/3); 104 | text(3, (xycals[1].x-xycals[0].x)/2+xycals[0].x-x, (xycals[4].y-y)/2-dy); 105 | } 106 | } else if (sx >= xycals[0].x && sx < xycals[1].x && sy >= xycals[3].y) { // 4th gear 107 | g = 4; 108 | if (showGear) { 109 | rect(xycals[0].x-x+2, -2, xycals[1].x-xycals[0].x-3, xycals[3].y-y-dy+4); 110 | fill(255); 111 | translate(-font_size/3, font_size/3); 112 | text(4, (xycals[1].x-xycals[0].x)/2+xycals[0].x-x, (xycals[3].y-y-dy)/2); 113 | } 114 | } else if (sx >= xycals[1].x && sx < xycals[2].x && sy < xycals[4].y) { // 5th gear 115 | g = 5; 116 | if (showGear) { 117 | rect(xycals[1].x-x+2, 1-dy, xycals[2].x-xycals[1].x-3, xycals[4].y-y-2); 118 | fill(255); 119 | translate(-font_size/3, font_size/3); 120 | text(5, (xycals[2].x-xycals[1].x)/2+xycals[1].x-x, (xycals[4].y-y)/2-dy); 121 | } 122 | } else if (sx >= xycals[1].x && sx < xycals[2].x && sy >= xycals[3].y) { // 6th gear 123 | g = 6; 124 | if (showGear) { 125 | rect(xycals[1].x-x+2, -2, xycals[2].x-xycals[1].x-3, xycals[3].y-y-dy+4); 126 | fill(255); 127 | String gr; 128 | if (Button[revGearBit] && bitRead(sConfig, 1) == 0) { // if bit1 of sConfig is LOW - 6 gear mode 129 | gr = "r"; 130 | } else { 131 | gr = "6"; 132 | } 133 | translate(-font_size/3, font_size/3); 134 | text(gr, (xycals[2].x-xycals[1].x)/2+xycals[1].x-x, (xycals[3].y-y-dy)/2); 135 | } 136 | } else if (sx >= xycals[2].x && sy < xycals[4].y) { // 7th gear 137 | g = 7; 138 | if (showGear) { 139 | rect(xycals[2].x-x+2, 1-dy, dx+x-xycals[2].x-2, xycals[4].y-y-2); 140 | fill(255); 141 | translate(-font_size/3, font_size/3); 142 | text(7, dx-(x+dx-xycals[2].x)/2, (xycals[4].y-y)/2-dy); 143 | } 144 | } else if (sx >= xycals[2].x && sy >= xycals[3].y) { // 8th gear 145 | g = 8; 146 | if (showGear) { 147 | rect(xycals[2].x-x+2, -2, dx+x-xycals[2].x-2, xycals[3].y-y-dy+4); 148 | fill(255); 149 | String gr; 150 | if (Button[revGearBit] && bitRead(sConfig, 1) == 1) { // if bit1 of sConfig is HIGH - 8 gear mode 151 | gr = "r"; 152 | } else { 153 | gr = "8"; 154 | } 155 | translate(-font_size/3, font_size/3); 156 | text(gr, dx-(x+dx-xycals[2].x)/2, (xycals[3].y-y-dy)/2); 157 | } 158 | } else { 159 | g = 0; 160 | if (showGear) { 161 | //rect(0, 0, 0, 0); 162 | //fill(255); 163 | } 164 | } 165 | popMatrix(); 166 | return g; 167 | } 168 | void setCal() { // get shifter calibration from wheel control 169 | for (int i=0; i xycals[i].limits[1]) { 234 | xycals[i].x = xycals[i].limits[1]; 235 | } 236 | } else { 237 | if (xycals[i].y <= xycals[i].limits[2]) { 238 | xycals[i].y = xycals[i].limits[2]; 239 | } else if (xycals[i].y > xycals[i].limits[3]) { 240 | xycals[i].y = xycals[i].limits[3]; 241 | } 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /code/sketch.properties: -------------------------------------------------------------------------------- 1 | mode=Java 2 | component=app 3 | mode.id=processing.mode.java.JavaMode 4 | -------------------------------------------------------------------------------- /data/Arduino Leonardo wheel v3: -------------------------------------------------------------------------------- 1 | Wheel 2 | Xaxis Steering axis 3 SLIDER X Axis 0 1.0 0.0 3 | Yaxis Brake 3 SLIDER Y Axis 0 1.0 0.0 4 | Zaxis Accelerator 3 SLIDER Z Axis 0 1.0 0.0 5 | Xrotation Cluch 3 SLIDER X Rotation 0 1.0 0.0 6 | 0 Button 0 1 BUTTON Button 0 0 0.0 0.0 7 | 1 Button 1 1 BUTTON Button 1 0 0.0 0.0 8 | 2 Button 2 1 BUTTON Button 2 0 0.0 0.0 9 | 3 Button 3 1 BUTTON Button 3 0 0.0 0.0 10 | 4 Button 4 1 BUTTON Button 4 0 0.0 0.0 11 | 5 Button 5 1 BUTTON Button 5 0 0.0 0.0 12 | 6 Button 6 1 BUTTON Button 6 0 0.0 0.0 13 | 7 Button 7 1 BUTTON Button 7 0 0.0 0.0 14 | 8 Button 8 1 BUTTON Button 8 0 0.0 0.0 15 | 9 Button 9 1 BUTTON Button 9 0 0.0 0.0 16 | 10 Button 10 1 BUTTON Button 10 0 0.0 0.0 17 | 11 Button 11 1 BUTTON Button 11 0 0.0 0.0 18 | 12 Button 12 1 BUTTON Button 12 0 0.0 0.0 19 | 13 Button 13 1 BUTTON Button 13 0 0.0 0.0 20 | 14 Button 14 1 BUTTON Button 14 0 0.0 0.0 21 | 15 Button 15 1 BUTTON Button 15 0 0.0 0.0 22 | 16 Button 16 1 BUTTON Button 16 0 0.0 0.0 23 | 17 Button 17 1 BUTTON Button 17 0 0.0 0.0 24 | 18 Button 18 1 BUTTON Button 18 0 0.0 0.0 25 | 19 Button 19 1 BUTTON Button 19 0 0.0 0.0 26 | 20 Button 20 1 BUTTON Button 20 0 0.0 0.0 27 | 21 Button 21 1 BUTTON Button 21 0 0.0 0.0 28 | 22 Button 22 1 BUTTON Button 22 0 0.0 0.0 29 | 23 Button 23 1 BUTTON Button 23 0 0.0 0.0 30 | 24 Button 24 1 BUTTON Button 24 0 0.0 0.0 31 | 25 Button 25 1 BUTTON Button 25 0 0.0 0.0 32 | 26 Button 26 1 BUTTON Button 26 0 0.0 0.0 33 | 27 Button 27 1 BUTTON Button 27 0 0.0 0.0 34 | 28 Button 28 1 BUTTON Button 28 0 0.0 0.0 35 | 29 Button 29 1 BUTTON Button 29 0 0.0 0.0 36 | 30 Button 30 1 BUTTON Button 30 0 0.0 0.0 37 | -------------------------------------------------------------------------------- /data/Arduino Leonardo wheel v4: -------------------------------------------------------------------------------- 1 | Wheel 2 | Xaxis Steering axis 3 SLIDER X Axis 0 1.0 0.0 3 | Yaxis Brake 3 SLIDER Y Axis 0 1.0 0.0 4 | Zaxis Accelerator 3 SLIDER Z Axis 0 1.0 0.0 5 | RXaxis Clutch 3 SLIDER X Rotation 0 1.0 0.0 6 | RYaxis Handbrake 3 SLIDER Y Rotation 0 1.0 0.0 7 | 0 Button 0 1 BUTTON Button 0 0 0.0 0.0 8 | 1 Button 1 1 BUTTON Button 1 0 0.0 0.0 9 | 2 Button 2 1 BUTTON Button 2 0 0.0 0.0 10 | 3 Button 3 1 BUTTON Button 3 0 0.0 0.0 11 | 4 Button 4 1 BUTTON Button 4 0 0.0 0.0 12 | 5 Button 5 1 BUTTON Button 5 0 0.0 0.0 13 | 6 Button 6 1 BUTTON Button 6 0 0.0 0.0 14 | 7 Button 7 1 BUTTON Button 7 0 0.0 0.0 15 | 8 Button 8 1 BUTTON Button 8 0 0.0 0.0 16 | 9 Button 9 1 BUTTON Button 9 0 0.0 0.0 17 | 10 Button 10 1 BUTTON Button 10 0 0.0 0.0 18 | 11 Button 11 1 BUTTON Button 11 0 0.0 0.0 19 | 12 Button 12 1 BUTTON Button 12 0 0.0 0.0 20 | 13 Button 13 1 BUTTON Button 13 0 0.0 0.0 21 | 14 Button 14 1 BUTTON Button 14 0 0.0 0.0 22 | 15 Button 15 1 BUTTON Button 15 0 0.0 0.0 23 | 16 Button 16 1 BUTTON Button 16 0 0.0 0.0 24 | 17 Button 17 1 BUTTON Button 17 0 0.0 0.0 25 | 18 Button 18 1 BUTTON Button 18 0 0.0 0.0 26 | 19 Button 19 1 BUTTON Button 19 0 0.0 0.0 27 | 20 Button 20 1 BUTTON Button 20 0 0.0 0.0 28 | 21 Button 21 1 BUTTON Button 21 0 0.0 0.0 29 | 22 Button 22 1 BUTTON Button 22 0 0.0 0.0 30 | 23 Button 23 1 BUTTON Button 23 0 0.0 0.0 31 | 24 Button 24 1 BUTTON Button 24 0 0.0 0.0 32 | 25 Button 25 1 BUTTON Button 25 0 0.0 0.0 33 | 26 Button 26 1 BUTTON Button 26 0 0.0 0.0 34 | 27 Button 27 1 BUTTON Button 27 0 0.0 0.0 35 | -------------------------------------------------------------------------------- /data/Arduino Leonardo wheel v5: -------------------------------------------------------------------------------- 1 | Wheel 2 | Xaxis Steering axis 3 SLIDER X Axis 0 1.0 0.0 3 | Yaxis Brake 3 SLIDER Y Axis 0 1.0 0.0 4 | Zaxis Accelerator 3 SLIDER Z Axis 0 1.0 0.0 5 | RXaxis Clutch 3 SLIDER X Rotation 0 1.0 0.0 6 | RYaxis Handbrake 3 SLIDER Y Rotation 0 1.0 0.0 7 | Hat Hat Switch 2 HAT cooliehat: Hat Switch 0 1.0 0.0 8 | 0 Button 0 1 BUTTON Button 0 0 0.0 0.0 9 | 1 Button 1 1 BUTTON Button 1 0 0.0 0.0 10 | 2 Button 2 1 BUTTON Button 2 0 0.0 0.0 11 | 3 Button 3 1 BUTTON Button 3 0 0.0 0.0 12 | 4 Button 4 1 BUTTON Button 4 0 0.0 0.0 13 | 5 Button 5 1 BUTTON Button 5 0 0.0 0.0 14 | 6 Button 6 1 BUTTON Button 6 0 0.0 0.0 15 | 7 Button 7 1 BUTTON Button 7 0 0.0 0.0 16 | 8 Button 8 1 BUTTON Button 8 0 0.0 0.0 17 | 9 Button 9 1 BUTTON Button 9 0 0.0 0.0 18 | 10 Button 10 1 BUTTON Button 10 0 0.0 0.0 19 | 11 Button 11 1 BUTTON Button 11 0 0.0 0.0 20 | 12 Button 12 1 BUTTON Button 12 0 0.0 0.0 21 | 13 Button 13 1 BUTTON Button 13 0 0.0 0.0 22 | 14 Button 14 1 BUTTON Button 14 0 0.0 0.0 23 | 15 Button 15 1 BUTTON Button 15 0 0.0 0.0 24 | 16 Button 16 1 BUTTON Button 16 0 0.0 0.0 25 | 17 Button 17 1 BUTTON Button 17 0 0.0 0.0 26 | 18 Button 18 1 BUTTON Button 18 0 0.0 0.0 27 | 19 Button 19 1 BUTTON Button 19 0 0.0 0.0 28 | 20 Button 20 1 BUTTON Button 20 0 0.0 0.0 29 | 21 Button 21 1 BUTTON Button 21 0 0.0 0.0 30 | 22 Button 22 1 BUTTON Button 22 0 0.0 0.0 31 | 23 Button 23 1 BUTTON Button 23 0 0.0 0.0 32 | -------------------------------------------------------------------------------- /data/COM_cfg.txt: -------------------------------------------------------------------------------- 1 | COM5 2 | -------------------------------------------------------------------------------- /data/TX_wheel_rim_small_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/TX_wheel_rim_small_alpha.png -------------------------------------------------------------------------------- /data/Wheel_control_gamecontrols_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/Wheel_control_gamecontrols_setup.png -------------------------------------------------------------------------------- /data/Wheel_control_step0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/Wheel_control_step0.png -------------------------------------------------------------------------------- /data/Wheel_control_step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/Wheel_control_step1.png -------------------------------------------------------------------------------- /data/Wheel_control_step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/Wheel_control_step2.png -------------------------------------------------------------------------------- /data/Wheel_control_step2a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/Wheel_control_step2a.png -------------------------------------------------------------------------------- /data/Wheel_control_step3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/Wheel_control_step3.png -------------------------------------------------------------------------------- /data/Wheel_control_v2_6_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/Wheel_control_v2_6_3.png -------------------------------------------------------------------------------- /data/Wheel_control_v2_6_3_dac_as5600_2ffb_axis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/Wheel_control_v2_6_3_dac_as5600_2ffb_axis.png -------------------------------------------------------------------------------- /data/Wheel_control_v2_6_3_pwm_as5600_2ffb_axis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/Wheel_control_v2_6_3_pwm_as5600_2ffb_axis.png -------------------------------------------------------------------------------- /data/arduino leonardo ffb wheel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/arduino leonardo ffb wheel.jpg -------------------------------------------------------------------------------- /data/axisColor_cfg.txt: -------------------------------------------------------------------------------- 1 | FFFF0000 2 | FFDDFF00 3 | FF00FF42 4 | FF009BFF 5 | FF8400FF 6 | -------------------------------------------------------------------------------- /data/guiChangeLog.txt: -------------------------------------------------------------------------------- 1 | Milos Rankovic, ranenbg@gmail.com 2 | created 09.04.2024 3 | last updated 02.04.2025. 4 | 5 | GUI change log from v2.5.1 to v2.6.0 6 | 7 | - added support for new firmware version and its options (v230, option "x") 8 | - added dropdown list for selecting which axis is tied to the FFB axis (allows analog axis for FFB, opt "x") 9 | - added advanced H-shifter option buttons (X,Y-axis invert, reverse gear button invert, only from v230f) 10 | - when other than X-axis are selected for FFB, animated sprite wheel angle is updated according to selected axis 11 | - improved handling of firmware options readout 12 | - improved handling of FFB monitor (if left running - auto stop on next startup) 13 | - added more startup info in the bottom of window (added complete firmware options readout) 14 | - minor H-shifter graphical updates and fixes 15 | - fixed a small bug where desktop user effects were not updated correctly at startup 16 | - minor change of some button names and/or descriptions (shifter, pedal calibration, save, store) 17 | - display HEX firmware version in window info 18 | - improved handling of how a profile is stored (overwrite check added) 19 | - axis max value gets updated according to the firmware features (default 1023, ads1105 4095, hx711 65535) 20 | - FFB effect slider values now show 100% instead of 1.0% 21 | 22 | GUI change log from v2.6.0 to v2.6.1 23 | 24 | - added encoder type indicator to display which one is enabled in the firmware 25 | - wheel buttons are automatically disabled (grayed out) if firmware doesn't support it (due to lack of available pins on Arduino) 26 | - improved firmware info startup diagnostics 27 | - rescaled FFB balance slider value (0=center value, -127 means 50% more pull on the left side, while 127 means 50% more pull on the right) 28 | this is purely visual while command functionality remains the same, see RSR232 commands info in repository for firmware for more details 29 | 30 | GUI change log from v2.6.1 to v2.6.2 31 | 32 | - added support for firmware "b" option (2 FFB axis with 4 PWM outputs) 33 | - added one more FFB monitor graph on top for displaying signal from 2nd FFB channel or Y FFB axis 34 | - added new "2CH PWM+-" option in dropdown menu for selecting PWM mode 35 | - improved firmware info startup diagnostics 36 | - added animation fade out effect on startup diagnostics info text 37 | - added logging startup diagnostics into a txt file 38 | - repositioned xy shifter calibration sliders such that one can set them more closely together 39 | - manual pedal calibration* is now stored and loaded from profile 40 | - xy shifter calibration/config* is now stored and loaded from profile 41 | - cleaned up leftover from old code (deleted some commented out parts, unused variables and classes) 42 | 43 | *If firmware doesn't support it, pressing store button will save default pedal/shifter calibration values in profile instead. After selecting such a profile in GUI, pedal/shifter calibration values are loaded in GUI memory, but are not applied to Arduino (no serial commands will be sent because they would be ignored by firmware). 44 | 45 | Note - to prevent accidental loss of pedal or shifter calibration, always first select an empty slot in profile dropdown menu and press store button to save your current firmware settings in PC. However, even if you changed some settings in GUI before saving a profile, Arduino will still keep the last saved settings in its EEPROM. Those firmware settings from Arduino EEPROM are overwritten only once you press save button. 46 | 47 | GUI change log from v2.6.2 to v2.6.3 48 | 49 | - added joystick 2-axis coordinate system view (for firmware with option "b" - 2 FFB axis) 50 | - added FFB vector display when FFB graph monitor is activated (FFB magnitude and direction are represented by a line) 51 | - in joystick view current axis positions are represented by a cursor cross 52 | - in joystick view values for both axis are displayed in the units of % 53 | - joystick view displays current axis labels (x-axis is configurable, y-axis is fixed for now) 54 | - joystick view is automatically displayed instead of a wheel rim sprite (if firmware "b" is detected) 55 | - minor graphical improvements to FFB monitor graph display (fixed sticking ticks, added left/right, up/down, xFFB and yFFB labels, FFB value is now inside graph) 56 | - added support for firmware option "d" (no optical encoder) 57 | - added inactivation feature of axis bar graph (z-axis becomes inactive for firmware "d", if no "w" option for magnetic encoder support) 58 | - added feature to automatically swap z- and x-axis (for firmware "d", if no "w" option) 59 | - x-axis calibration limits c and d are borrowed from z-axis (for firmware "d", if no "w" option) 60 | - x-axis calibration is copied from z-axis on program startup, saving/loading settings from profile or pressing defaults button (for firmware "d", if no "w" option) 61 | - adjusting x-axis calibration limits automatically update inactivated z-axis calibration limits (for firmware "d", if no "w" option) 62 | - xFFB-axis dropdown menu selector shows "x-pot" instead of "x-enc" item, for firmware "d" if no "w" option (no digital encoders) 63 | - renamed "fast top" into "fast pwm" in dropdown menu for adjusting PWM type 64 | - added support for new firmware option "p" - when no EEPROM is used 65 | - changed how firmware version string is interpreted starting from HEX v250 (all 3 digits now represent firmware version only) 66 | - added support for new firmware options: "n"- nano button box, "l"- load cell and "g"- external dac) 67 | - GUI will use old firmware version string interpretation for all firmware before v250 (backwards compatibility with old firmware is maintained) 68 | - added dropdown menu selector in dac mode for enabling/disabling dac output (if disabled, a corresponding zero will be applied for each mode) 69 | - added support for other 2CH pwm modes (2CH PWM+DIR, 2CH PWM0-50-10 and 2CH RCM) 70 | - added support for new dac mode and its 2 channel equivalents (DAC0-50-100 and 2CH DAC0-50-100, 2CH DAC+DIR) 71 | - squashed a few small leftover bugs connected with configuring pwm/dac settings 72 | 73 | GUI change log from v2.6.3 to v2.6.4 74 | 75 | - added support for firmware option "u" (support for two magnetic encoders AS5600) 76 | - inactivated y-axis calibration limits (only for firmware "w" + "u") 77 | - rescaled y-axis bar graph max value to 65535 if 2nd magnetic encoder is used (only for firmware "w" + "u") 78 | - added support for firmware option "n" + "r" (for 24 buttons via 3x8bit SN74ALS166N or SN74HC165N shift register chips) 79 | - corrected inactivation of unavailable buttons when using shift registers and hat switch -------------------------------------------------------------------------------- /data/manual.txt: -------------------------------------------------------------------------------- 1 | Milos Rankovic 2 | ranenbg@gmail.com 3 | 06.03.2025. 4 | wheel control v2.6.4 - compatible with Arduino HEX versions fw-v250 (backward compatible with v170, v180, v190, v200, v210, v220, v230 and v240) 5 | 6 | About: 7 | Wheel control GUI is made in windows programming environment called Processing v3.5.4, which is based on Java. It operates by directly reading HID values from Arduino such as axis and buttons. It uses RS232 or virtual serial port to send many settings that you may like to adjust (check firmware_info.txt for more details). The settings can be stored in Arduino EEPROM, such that they will be saved and automatically loaded at every powerup. Arduino firmware and GUI are developed and tested in Win7/10/11. 8 | You can use Arduino Leonardo, Arduino Micro or Arduino Pro Micro boards. However, hey need to have a correct bootloader. You can verify this by pressing restart button on the board. If the Arduino enters bootloader mode for about 8sec it is ok - LED will slowly blink during this time. If it instantly restarts, then you will have to upload a bootloader manually. Complete description of that procedure is outside of the scope of this manual, for details see https://www.arduino.cc/en/Tutorial/BuiltInExamples/ArduinoISP. 9 | 10 | How to start wheel control: 11 | [0] upload desired HEX file to your Arduino using XLoader 12 | [1] configure the Gamepad input library (pink window) 13 | [2] configure correct COM port for your Arduino board 14 | 15 | There are two ways to run my wheel control program. One is by clicking on exe file for which you may need the latest version of Java to be installed on your PC. The second option is to install Processing IDE v3.5.4 on your PC and run wheel_control.pde from it. You will need to install a few missing libraries. Pay attention that wheel_control.pde with all other *.pde files must be located in the folder with the same name wheel_control. Be careful with this after downloading GUI repository from GitHub. 16 | When 1st starting wheel control (either exe or source code), a one time configuration will be needed. A pink window will appear showing two sides of buttons and axis. You need to connect each one by dragging a line. Just start from top and work your way to the bottom. Once done, click verify button. If no errors appear click use button. 17 | Now a dialog window will appear and you will need to select at which COM port is your Arduino, by typing one of the letters (a, b, c,...). This is required only once, since selected COM port will be saved in COM_cfg.txt file. If the number of COM devices is not changed by plugging or unplugging any other COM devices, Arduino should stay at the same COM port even after re-plug. If there was a change, go in data folder of wheel control and delete COM_cfg.txt file. Start wheel control and you will see window for selecting COM port again. File COM_cfg.txt will be saved again with new COM port. Optionally you can just manually create/edit COM_cfg.txt with a correct COM port. The COM port selection window only appears if there is no COM_cfg.txt present in data folder of wheel control. Contents of such file should be for example COM5. 18 | 19 | How to use wheel control: 20 | [0] set your encoder's CPR=4*PPR*GR, where GR (gear ratio) is 1 if you mounted encoder on wheel shaft 21 | [1] set desired Rotation degrees 22 | [2] manually align your wheel to center position, press center button and then save button 23 | [3] select PWM type (phase correct is recommended, but you can use fast top for twice higher frequency at the same resolution) 24 | [4] select PWM mode (pwm+-, pwm+dir, pwm0.50.100-use or rcm), you need pwm+- for BTS7960 25 | [5] select PWM frequency (check firmware_info.txt for more details), ideally 8kHz or less. 26 | [6] press pwm button and close wheel control, restart or re-plug Arduino to apply new PWM settings, then start wheel control again 27 | 28 | The firmware supports user FFB effects or sometimes called desktop effects. These are "always on" effects that will be added on top of any other FFB effects which other games or application may send. You may enable certain desktop effects like spring, damper, inertia or friction by pressing a square button next to the corresponding slider. These effects may be useful for DD wheels, to simulate some realistic mechanical properties of an AC motor like moment of inertia, damping or friction. 29 | Use general gain slider to set the master gain for all FFB effects. You may use min torque PWM slider to compensate for startup current and system friction when using other motor types than AC servo motors. It is very useful for DC brushed motors especially. 30 | The red square button next to general gain slider is for enabling/disabling real time FFB monitor graph. It is extremely useful for troubleshooting or fine tunning your FFB settings in the game and finding the point of clipping. It is directly showing an FFB signal over COM port, that a game is sending in 1sec time window. It is recommended not to keep it always on, its purpose is only for FFB signal inspection and making sure there is no clipping. Once you are happy with your FFB level, disable the FFB monitor not to cause any potential delays due to COM data transfer. 31 | New feature (v.2.6.0) is profile dropdown menu. Select one of the empty slots first. Make changes to the FFB settings as you wish and click store button. Type some name and click ok. This will save all firmware settings into a profile.txt file. At each startup wheel control will look for any existing profiles and load them in program memory. Once selected from drop down menu, firmware settings from that profile are applied to Arduino within a few milliseconds. Note that PWM settings are stored in profile txt but they are not applied to Arduino upon loading. New feature (v2.6.0) is that pedal and shifter calibration are also stored/loaded in profile. If firmware doesn't support it, pressing store button will save default pedal/shifter calibration values in profile instead. After loading such a profile in GUI, pedal/shifter calibration values from it are loaded in program memory, but are not applied to Arduino (no serial commands will be sent). To prevent accidental loss of pedal or shifter calibration, always first select an empty slot in profile dropdown menu and store your current settings to be safe. However, even if you changed them in GUI, Arduino will still keep the last saved settings in its EEPROM. Those values from Arduino EEPROM are overwritten only if you press save button. 32 | 33 | Additional startup troubleshooting has been added from v2.0, such that startup problems will be easier to diagnose. Window is no longer white, and it shows some advanced setup info. Since v2.4 I have improved the text info messages in each window from setup process, to contain some more details and to be more user friendly. Since v2.6.2 I have further improved startup diagnostics and a log txt file is created. 34 | 35 | Axis color setup: 36 | The axis colors are stored in a axisColor.txt file in a HEX format. This file will be created at startup with default values if it does not already exist. If it exists then the axis colors will be loaded from it. First two letters are alpha channel, so you can leave this at FF and fill in the remaining 6 numbers. In following link you can get those 6 HEX numbers from RGB values. 37 | https://www.rapidtables.com/convert/color/rgb-to-hex.html 38 | 39 | XY analog shifter calibration/config: 40 | This version of wheel control allows you to setup an analog shifter. The shifter supports 8 gears + reverse, while you can edit its calibration limits by pressing "shifter" button. There are 5 pointers (sliders) that you can move with mouse by dragging. Make sure to release mouse left button while the cursor is still inside the pointer in order for change to take place. Once happy, you can click "save" button to store shifter calibration to Arduino (save values in EEPROM). There is an additional button that allows you to configure where a reverse gear will be. If the small button is red (not activated) the shifter is configured to 6 gears + reverse, while if the button is green (activated) then the reverse is in 8th gear. To activate reverse gear you need to press button0 (by default it's at Arduino pin D4, but may be elsewhere depending on firmware version and options). You may use additional shifter options available only from fw-v230f, to invert shifter X or Y axis or invert the reverse gear button (Logitech h-shifter support). 41 | 42 | Manual pedal axis calibration: 43 | In this version you can manual set pedal axis calibration limits and save them into Arduino EEPROM. The calibration values are automatically loaded at each powerup of Arduino. Each time you start wheel control it will ask Arduino for latest calibration values and update the sliders accordingly. In order to set calibration limits first press "man. pcal" button to unhide the calibration sliders. Move sliders to their lowest and maximum positions if they are not already there. Now push each pedal to its full range and set its corresponding maximum slider to a value slightly below the pedal axis value. Once done, now move back each pedal into there lowest position and set the minimum slider to a value slightly above the pedal axis value. Pedal axis values should move full range if done correctly. Once happy with your pedal travel and calibration limits you can press "save" button to save settings in Arduino EEPROM. 44 | 45 | RCM pwm mode settings: 46 | Some RC servos and other brushless motor drivers require a special pwm mode for their operation, called RCM or PPM. This version of wheel control supports the firmware version fw-v21X where I have added this new pwm mode. Note that not all frequencies are available due to the nature of this mode. A zero force is represented as a square wave with a pulse width of 1.5ms, while full left force has 1.0ms pulse width and full right force has 2.0ms pulse width. This imposes a limit for the max allowed frequency of 500Hz which corresponds to 2ms period. Any higher frequency than 500Hz would require a lower period, therefore it does not allow to achieve the full range of right (positive) forces. For that reason I have labeled such frequencies as NA - not available. I have implemented some safety features in GUI, such that you can't select or send incorrect pwm settings to firmware. 47 | 48 | Using encoder with a Z-index: 49 | If your encoder has a 3rd channel called Z you can use HEX firmware with "z" option to automatically detect the zero wheel angle (for this you must couple encoder to wheel shaft with exactly 1:1 ratio). Such encoders have one additional slit in their optical disc that generates Z-index pulse. Because there is just one such slit on full 360deg, we can extract an absolute angle information. It may happen that you mounted your encoder in such an orientation that its Z-index pulse does not align with real 0 wheel angle. Ideally, you want this to be somewhere near the real 0deg (but you can never perfectly match it). Since v2.6.0 of wheel control in combination with firmware v220z (and later) you can set/reset the Z-index pulse angle offset in the firmware. I will now describe the procedure. After "z" firmware upload and first start of wheel control you need to rotate your wheel/encoder until a Z-index pulse is found. When this happens, a virtual wheel in GUI will reposition to 0deg and encoder state will be set to 1 (use s key to verify). Then re-align your wheel to a real 0 angle and press center button. At that moment a new Z-index offset angle is established as a difference between previous 2 angles. Pressing the save button will save this angle offset in Arduino EEPROM. Upon each Arduino powerup, as soon as Z-index pulse has been detected this offset will be automatically applied and corrected wheel angle will be used. If you are not happy with the centering and wish to start again, you may press the z button first to reset Z-index angle offset to 0 and repeat the above described process. Tip - I use a marker to place a dot on the top side of encoder enclosure, referenced against flat portion of encoder D-shaft. This helps greatly when mounting encoder as you can keep track of Z-index angle position, such that you may orient it close to a real wheel center (within +-30 deg is typical). 50 | 51 | Using FFB axis selector: 52 | You can select which axis is tied to FFB with a dropdown list selector. If you select any other axis than X (encoder axis), then you can utilize analog axis as FFB input. This means that all internal and user FFB effects will also work in addition to the FFB effects that the game is sending. In that case you have to consider the rotation angle as this will depend on the sensor you use. Potentiometers normally have about 270-300deg range, while some can have more than 1 turn. If you use a hall sensor, then consider the mechanics which determine final angular range for your axis. This is only visual, it doesn't change anything, because axis range is anyway in arbitrary units as seen by the game or windows. You can set CPR to configure how much axis range you can utilize. Normally CPR of 1000 and 300deg rotation is fine for analog axis on a potentiometer. Bare in mind that manual or automatic calibration ranges for pedals (analog) axis will also have their effect on the final resolution of you analog input steering axis for FFB. 53 | 54 | Using 2-axis FFB: 55 | Since v2.6.3 and firmware HEX v250b and above, a feature to use 2 FFB axis is supported. This allows you to use Arduino as a flight controller where x-axis is input for xFFB and y-axis for yFFB. Correspondingly, there are 4 PWM channels configured in dual PWM+- mode for H-bridge type of DC motor drivers. First PWM channel is on D9, D10 (left, right) and 2nd PWM channel on pins D11, D5(up, down). Additionally, firmware HEX v250bd allows you to use 2 analog inputs for x- and y-axis. At the moment, x-axis analog input is selectable, while y-axis is fixed to the brake pedal input. 56 | 57 | Hopefully everything else will be self explanatory, I believe that this is an intuitive and user friendly GUI. Enjoy :) 58 | rane. 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /data/processing_3_5_4_status_log.txt: -------------------------------------------------------------------------------- 1 | 2 | ======================================================= 3 | Arduino-FFB-Wheel Graphical User Interface 4 | Wheel Control v2.6.3 created by 5 | Milos Rankovic 2018-2025 6 | ======================================================= 7 | GameControlPlus V1.2.2 created by 8 | Christian Riekoff and Peter Lager 9 | ======================================================= 10 | Instance: org.gamecontrolplus.ControlIO@62ea8962 11 | ########################################################################################## 12 | Game Control Plus - available devices 13 | -------------------------------------- 14 | 0 Mouse [Mouse] on [Unknown] 15 | 1 Keyboard [Keyboard] on [Unknown] 16 | 2 G203 LIGHTSYNC Gaming Mouse [Unknown] on [Unknown] 17 | 3 Virtual HID Framework (VHF) HID device [Unknown] on [Unknown] 18 | 4 Arduino Leonardo [Stick] on [Unknown] 19 | 5 Dell KB216 Wired Keyboard [Unknown] on [Unknown] 20 | 6 Dell KB216 Wired Keyboard [Unknown] on [Unknown] 21 | 7 G203 LIGHTSYNC Gaming Mouse [Unknown] on [Unknown] 22 | 8 G203 LIGHTSYNC Gaming Mouse [Unknown] on [Unknown] 23 | 9 G203 LIGHTSYNC Gaming Mouse [Unknown] on [Unknown] 24 | ########################################################################################## 25 | 26 | HID device: Arduino Leonardo 27 | Exported setupTextLog.txt 28 | Port: COM5, loaded from txt 29 | Axis colors: loaded from txt 30 | ======================================================= 31 | G4P V4.3.11 created by Peter Lager 32 | ======================================================= 33 | Firmware settings detected 34 | 360.0 1.0 0.5 0.5 1.0 1.0 1.0 0.5 0.7 1.0 0.0 128.0 1 4095 4096 34; 7ms 35 | Reading firmware version 36 | WB:V, RB:fw-v250bdwxg; 13ms 37 | MCP4725 detected 38 | Analog axis for FFB enabled 39 | AS5600 detected 40 | No optical encoder supported 41 | 2 FFB axis detected 42 | Manual pcal enabled 43 | WB:YR, RB:118 863 6 992 18 988 10 990; 16ms 44 | Using EEPROM to load/save settings 45 | WB:V, RB:fw-v250bdwxg; 17ms 46 | ControlP5 2.2.6 infos, comments, questions at http://www.sojamo.de/libraries/controlP5 47 | profile1.txt loaded in memory 48 | profile2.txt loaded in memory 49 | profile3.txt loaded in memory 50 | profile4.txt loaded in memory 51 | Exported setupTextLog.txt -------------------------------------------------------------------------------- /data/profile1.txt: -------------------------------------------------------------------------------- 1 | rane 2 | 1080.0 3 | 1.0 4 | 0.5 5 | 0.5 6 | 1.0 7 | 1.0 8 | 1.0 9 | 0.5 10 | 0.7 11 | 1.0 12 | 0.0 13 | 45.0 14 | 1.0 15 | 10000.0 16 | 4000.0 17 | 33.0 18 | 10 1023 10 1023 10 1023 10 1023 19 | 425 525 621 447 543 0 20 | -------------------------------------------------------------------------------- /data/profile2.txt: -------------------------------------------------------------------------------- 1 | affb 2 | 300.0 3 | 1.0 4 | 0.5 5 | 0.5 6 | 1.0 7 | 1.0 8 | 1.0 9 | 0.5 10 | 1.0 11 | 1.0 12 | 0.0 13 | 128.0 14 | 65.0 15 | 2047.0 16 | 1000.0 17 | 12.0 18 | 0 1023 0 1023 0 1023 0 1023 19 | 377 493 613 447 563 0 20 | -------------------------------------------------------------------------------- /data/profile3.txt: -------------------------------------------------------------------------------- 1 | ninja 2 | 1080.0 3 | 1.0 4 | 0.5 5 | 0.5 6 | 1.0 7 | 1.0 8 | 1.0 9 | 0.5 10 | 0.7 11 | 1.0 12 | 0.0 13 | 128.0 14 | 1.0 15 | 10000.0 16 | 2400.0 17 | 33.0 18 | 0 1023 0 1023 0 1023 0 1023 19 | 509 529 553 479 503 0 20 | -------------------------------------------------------------------------------- /data/profile4.txt: -------------------------------------------------------------------------------- 1 | xyffb 2 | 360.0 3 | 1.0 4 | 0.5 5 | 0.5 6 | 1.0 7 | 1.0 8 | 1.0 9 | 0.5 10 | 0.1973545 11 | 1.0 12 | 0.0 13 | 128.0 14 | 1.0 15 | 2047.0 16 | 2047.0 17 | 12.0 18 | 57 875 14 973 67 965 96 941 19 | 255 511 767 255 511 2 20 | -------------------------------------------------------------------------------- /data/rane_wheel_rim_D-shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/rane_wheel_rim_D-shape.png -------------------------------------------------------------------------------- /data/rane_wheel_rim_O-shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ranenbg/Arduino-FFB-gui/6abb01c3036c0d58caaf00ca4e7d3d1e6f36d821/data/rane_wheel_rim_O-shape.png -------------------------------------------------------------------------------- /data/setupTextLog.txt: -------------------------------------------------------------------------------- 1 | Wheel control v2.6.4 configuration initialized 2 | HID device: Arduino Leonardo 3 | Port: COM5, loaded from txt 4 | Axis colors: loaded from txt 5 | Firmware settings detected 6 | 360 100 50 50 100 100 100 50 10 100 0 128 1 2047 4095 12 7 | HEX version: fw-v250bdwux 8 | Analog axis for FFB enabled 9 | AS5600 magnetic encoder detected 10 | TCA9548A i2C multiplexer detected 11 | No optical encoder supported 12 | 2 FFB axis and 2 channel PWM output enabled 13 | Manual calibration for pedals enabled 14 | 128 863 28 998 81 982 79 980 15 | Using EEPROM to load/save settings 16 | Configuration done 17 | -------------------------------------------------------------------------------- /wheel_control.pde: -------------------------------------------------------------------------------- 1 | /*Arduino Force Feedback Wheel User Interface 2 | 3 | Copyright 2018-2025 Milos Rankovic (ranenbg [at] gmail [dot] com) 4 | 5 | Permission to use, copy, modify, distribute, and sell this 6 | software and its documentation for any purpose is hereby granted 7 | without fee, provided that the above copyright notice appear in 8 | all copies and that both that the copyright notice and this 9 | permission notice and warranty disclaimer appear in supporting 10 | documentation, and that the name of the author not be used in 11 | advertising or publicity pertaining to distribution of the 12 | software without specific, written prior permission. 13 | 14 | The author disclaim all warranties with regard to this 15 | software, including all implied warranties of merchantability 16 | and fitness. In no event shall the author be liable for any 17 | special, indirect or consequential damages or any damages 18 | whatsoever resulting from loss of use, data or profits, whether 19 | in an action of contract, negligence or other tortious action, 20 | arising out of or in connection with the use or performance of 21 | this software. 22 | */ 23 | 24 | import org.gamecontrolplus.gui.*; 25 | import org.gamecontrolplus.*; 26 | import net.java.games.input.*; 27 | import g4p_controls.*; 28 | import processing.serial.*; 29 | import sprites.*; 30 | import sprites.maths.*; 31 | import sprites.utils.*; 32 | import controlP5.*; 33 | import java.util.*; 34 | import static javax.swing.JOptionPane.*; 35 | import javax.swing.JFrame.*; 36 | 37 | String cpVer="v2.6.4"; // control panel version 38 | 39 | Serial myPort; // Create object from Serial class 40 | String rb; // Data received from the serial port 41 | String wb; // Data to send to the serial port 42 | final boolean debug = true; 43 | 44 | Sprite[] sprite = new Sprite[1]; 45 | Domain domain; 46 | 47 | ControlP5 cp5; // Editable Numberbox for ControlP5 48 | Numberbox num1; // create instance of numberbox class 49 | 50 | int num_axis = 5; // number of axis to display 51 | int num_sldr = 12; //number of FFB sliders 52 | int num_btn = 24; //number of wheel buttons 53 | int ctrl_btn = 18; //number of control buttons 54 | int ctrl_sh_btn = 5; //number of control buttons for XY shifter 55 | int ctrl_axis_btn = 10; //number of control buttons for gamepad axis 56 | int key_btn = 12; //number of keyboard function buttons 57 | int gbuffer = 500; //number of points to show in ffb graph 58 | int gskip = 8; //ffb monitor graph vertical divider 59 | int num_profiles = 64; //number of FFB setting profiles (including default profile) 60 | int num_prfset = 18; //number of FFB settings inside a profile 61 | int cur_profile; // currently loaded FFB settings profile 62 | String[] command = new String[num_sldr]; // commands for wheel FFB parameters set 63 | float[] wParmFFB = new float[num_sldr]; // current wheel FFB parameters 64 | float[] wParmFFBprev = new float[num_sldr]; // previous wheel FFB parameters 65 | float[] defParmFFB = new float[num_sldr]; // deafault wheel FFB parameters 66 | GCustomSlider[] sdr = new GCustomSlider [num_sldr]; 67 | String[] sliderlabel = new String[num_sldr]; 68 | float[] slider_value = new float[num_sldr]; 69 | boolean parmChanged = false; // keep track if any FFB parm was changed 70 | boolean wheelMoved = false; // keep track if wheel axis is centered 71 | float prevaxis = 0.0; // previous steer axis value 72 | int axisScale = 250; // length of axis ruler axisScale 73 | float posY; 74 | int[] col = new int[3]; // colors for control buttons, hsb mode 75 | int thue; // color for text of control button, gray axisScale mode 76 | boolean[] buttonpressed = new boolean[ctrl_btn]; // true if button is pressed 77 | String[] description = new String[key_btn]; // keyboard button function description 78 | String[] keys = new String[key_btn]; // keyboard buttons 79 | boolean enableinfo = true; 80 | boolean dActByp = true; // if true, it will bypass wheel buttons' ability to be inactivated (gray) 81 | byte effstate, effstateprev, effstatedef; // current, previous and default desktop effect state in binary form 82 | byte pwmstate, pwmstateprev, pwmstatedef; // current, previous and default pwm settings in binary form 83 | boolean typepwm; // keeps track of PWM type settings 84 | int freqpwm, modepwm; // keeps track of PWM frequency index selection and pwm mode settings 85 | int minTorque, maxTorque, maxTorquedef; // min, max ffb value or PWM steps 86 | int curCPR, lastCPR, CPRdef; 87 | int maxCPR = 99999; // maximum acceptable CPR by firmware 88 | float deg_min = 30.0; // minimal allowed angle by firmware 89 | float deg_max = 1800.0; // maximal allowed angle by firmware 90 | int maxCPR_turns = maxCPR*int(deg_max/360.0); // maximum acceptable CPR*turns by firmware 91 | float minPWM_max = 20.0; // maximum allowed value for minPWM 92 | float brake_min = 1.0; // minimal brake pressure 93 | float brake_max = 255.0; // max brake pressure 94 | boolean fbmnstp = false; // keeps track if we deactivated ffb monitor 95 | String fbmnstring; // string from ffb monitor readout 96 | String COMport[]; // string for serial port on which Arduino Leonardo is reported 97 | boolean BBenabled = false; // keeps track if button box is supported (3 digit fw ending with 1, only up to v240) 98 | boolean BMenabled = false; // keeps track if button matrix is supported (option "t") 99 | boolean LCenabled = false; // keeps track if load cell is supported (3 digit fw ending with 2, only up to v240) 100 | boolean DACenabled = false; // keeps track if FFB DAC output is supported in firmware (3 digit fw ending with 3, only up to v240) 101 | boolean TCA9548enabled = false; // keeps track if multiplexer chip is supported in firmware (option "u") 102 | boolean checkFwVer = true; // when enabled update fwVersion will take place 103 | boolean enabledac; // keeps track if DAC output is not zeroed 104 | int modedac; // keeps track of DAC output settings 105 | boolean profileActuated = false; // keeps track if we pressed the profile selection 106 | boolean CPRlimit = false; // true if we input more than max allowed CPR 107 | boolean pwm0_50_100enabled = false; // true if firmware supports pwm0.50.100 mode 108 | boolean pwm0_50_100selected = false; // keeps track if pwm0.50.100 mode is selected 109 | boolean RCMenabled = false; // true if firmware supports RCM pwm mode 110 | boolean RCMselected = false; // keeps track if RCM pwm mode is selected 111 | boolean AFFBenabled = false; // keeps track if analog FFB axis is available 112 | int rbt_ms = 0; // read buffer response time in milliseconds 113 | String FullfwVerStr; // Arduino firmware version including the options 114 | String fwVerStr; // Arduino firmware version not including the options 115 | int fwVerNum; // Arduino firmware version digits only 116 | byte fwOpt = 0; // Arduino firmware options 1st byte, if present bit is HIGH (b0-a, b1-z, b2-h, b3-s, b4-i, b5-m, b6-t, b7-f) 117 | byte fwOpt2 = 0; // Arduino firmware options 2nd byte, if present bit is HIGH (b0-e, b1-x, b2-w, b3-c, b4-r, b5-b, b6-d, b7-p) 118 | byte fwOpt3 = 0; // Arduino firmware options 3rd byte, if present bit is HIGH (b0-, b1-l, b2-g, b3-u, b4-, b5-, b6-, b7-) 119 | boolean noOptEnc = false; // true if firmware with "d" option without optical encoder support 120 | boolean noMagEnc = true; // false if firmware with "w" option which supports magnetic encoder AS5600 121 | boolean clutchenabled = true; // true if firmware supports clutch analog axis (not the case only for fw option "e") 122 | boolean hbrakeenabled = true; // true if firmware supports handbrake analog axis (not the case only for fw option "e") 123 | boolean twoFFBaxis_enabled = false; // true if firmware supports 2 FFB axis (option "b") 124 | int Xoffset = -44; // X-axis offset for buttons 125 | boolean XYshifterEnabled = false; // keeps track if XY analog shifter is supported by firmware 126 | int shifterLastConfig[] = new int[6]; // last XY shifter calibration and configuration settings 127 | String[] shCommand = new String[ctrl_sh_btn+3]; // commands for XY shifter settings 128 | int[] xysParmDef = new int [6]; // XY shifter defaults 129 | String[] pdlCommand = new String[9]; // commands for pedal calibration 130 | float[] pdlMinParm = new float [num_axis-1]; // curent pedal minimum cal values 131 | float[] pdlMaxParm = new float [num_axis-1]; // curent pedal maximum cal values 132 | float[] pdlParmDef = new float [2*(num_axis-1)]; // default pedal cal values 133 | int pdlDefMax = 1023; // default pedal Max calibration value for standard firmware 134 | // pwm frequency selection possibilities - depends on firmware version and RCM mode 135 | List a = Arrays.asList("40.0 kHz", "20.0 kHz", "16.0kHz", "8.0 kHz", "4.0 kHz", "3.2 kHz", "1.6 kHz", "976 Hz", "800 Hz", "488 Hz"); // for fw-v200 or lower 136 | List a1 = Arrays.asList("40.0 kHz", "20.0 kHz", "16.0kHz", "8.0 kHz", "4.0 kHz", "3.2 kHz", "1.6 kHz", "976 Hz", "800 Hz", "488 Hz", "533 Hz", "400 Hz", "244 Hz"); // wider pwm freq selection (fw-v210+), no RCM selected 137 | List a2_rcm = Arrays.asList("na", "na", "na", "na", "500 Hz", "400 Hz", "200 Hz", "122 Hz", "100 Hz", "61 Hz", "67 Hz", "50 Hz", "30 Hz"); // alternate pwm freq selection if RCM selected 138 | int allowedRCMfreqID = 4; // first allowed pwm freq ID for RCM mode from the above list (anything after and including 500Hz is allowed) 139 | int xFFBAxisIndex; // index of axis that is tied to the xFFB axis (bits 5-7 from effstate byte) 140 | int yFFBAxisIndex = 1; // index of axis that is tied to the yFFB axis (at the moment fixed to y-axis in firmware) 141 | int setupTextLines = 24; // number of available lines for text in configuration window 142 | String[] setupTextBuffer = new String[setupTextLines]; // array that holds all text for configuration window 143 | int setupTextTimeout_ms = 5000; // show setup text only during this timeout in [ms] 144 | int setupTextFadeout_ms = 1500; // durration of setup text fadeout at the end of timeout [ms] 145 | boolean showSetupText = true; // set to false after timeout + fadeout 146 | float setupTextInitAlpha = 255; // initial setup text alpha before fadeout 147 | float setupTextAlpha; // current text setupTextInitAlpha 148 | int setupTextLength = 0; // keeps track of how many lines of text we have in the setup text buffer 149 | int ffbx = 0; // x-axis FFB value from FFB monitor 150 | int ffby = 0; // y-axis FFB value from FFB monitor 151 | 152 | ControlIO control; 153 | Configuration config; 154 | ControlDevice gpad; 155 | 156 | // gamepad axis array 157 | float[] Axis = new float[num_axis]; 158 | float axisValue; 159 | float axisScaledValue; 160 | // gamepad button array 161 | boolean[] Button = new boolean [num_btn]; 162 | boolean buttonValue = false; 163 | // gamepad D-pad 164 | //int[] Dpad = new int[8]; 165 | int hatvalue; 166 | // control buttons 167 | boolean[] controlb = new boolean[ctrl_btn+ctrl_sh_btn+ctrl_axis_btn]; // true as long as mouse is howered over 168 | 169 | PFont font; 170 | int font_size = 12; 171 | 172 | int slider_width = 400; 173 | int slider_height = 110; 174 | int sldXoff = 100; 175 | float slider_max = 2.0; 176 | color[] axis_color = new color [num_axis]; 177 | 178 | Wheel[] wheels = new Wheel [1]; 179 | Slajder[] slajderi = new Slajder[num_axis]; 180 | Dugme[] dugmici = new Dugme[num_btn]; 181 | HatSW[] hatsw = new HatSW[1]; 182 | Dialog[] dialogs = new Dialog [1]; 183 | Button[] buttons = new Button[ctrl_btn]; 184 | Info[] infos = new Info[key_btn]; 185 | FFBgraph[] ffbgraphs = new FFBgraph[2]; 186 | Profile[] profiles = new Profile[num_profiles]; 187 | XYshifter[] shifters = new XYshifter[1]; 188 | InfoButton[] infobuttons = new InfoButton [1]; 189 | 190 | void setup() { 191 | size(1440, 800, JAVA2D); 192 | colorMode (HSB); 193 | frameRate(100); 194 | //noSmooth(); 195 | smooth(2); 196 | background(51); 197 | //PImage icon = loadImage("/data/rane_wheel_rim_O-shape.png"); 198 | //surface.setIcon(icon); 199 | println("=======================================================\n Arduino-FFB-Wheel Graphical User Interface\t\n Wheel Control "+cpVer +" created by\t\n Milos Rankovic 2018-2025"); 200 | clearSetupTextBuffer(); 201 | showSetupTextLine("Wheel control "+cpVer+" configuration initialized"); 202 | File f = new File(dataPath("COM_cfg.txt")); 203 | //https://docs.oracle.com/javase/tutorial/uiswing/components/dialog.html 204 | if (!f.exists()) showMessageDialog(frame, "COM_cfg.txt was not found in your PC, but do not worry.\nYou either run the app for the 1st time, or you have\ndeleted the configuration file for a fresh start.\n\t\nPress OK to continue with the automatic setup process.", "Arduino FFB Wheel " + cpVer +" - Hello World :)", INFORMATION_MESSAGE); 205 | if (!f.exists()) showMessageDialog(frame, "Setup will now try to find control IO instances.\n", "Setup - step 1/3", INFORMATION_MESSAGE); 206 | // Initialise the ControlIO 207 | //showSetupTextLine("Initializing IO instances"); 208 | control = ControlIO.getInstance(this); 209 | println("Instance:", control); 210 | // Find a device that matches the configuration file 211 | if (!f.exists()) showMessageDialog(frame, "Step 1 of setup has passed succesfully.\nSetup will now try to look for available game devices in your PC.\n", "Setup - step 2/3", INFORMATION_MESSAGE); 212 | String inputdevices = ""; 213 | inputdevices = control.deviceListToText(""); 214 | if (!f.exists()) showMessageDialog(frame, "\nThe following devices are found in your PC:\n\t\n"+inputdevices+"\nThe setup will now try to configure each device, but bare in mind that some devices may cause the app to crash.\nIf that happens, you may try to manually create COM_cfg.txt file (see manual.txt in data folder for instructions),\nor you may try to run wheel_control.pde source code from Processsing IDE version 3.5.4.\n", "Setup - list of available devices", INFORMATION_MESSAGE); 215 | println(inputdevices); 216 | //showSetupTextLine("Looking for compatible devices"); 217 | gpad = control.getMatchedDevice("Arduino Leonardo wheel v5"); 218 | if (gpad == null) { 219 | println("No suitable HID device found"); 220 | showSetupTextLine("No suitable HID device found"); 221 | System.exit(-1); // End the program NOW! 222 | } else { 223 | showSetupTextLine("HID device: " + gpad); 224 | println("HID device:", gpad); 225 | } 226 | int r; 227 | //println(" config:",f.exists()); 228 | if (f.exists()) { // if there is COM_cfg.txt, load serial port number from cfg file 229 | COMport = loadStrings("/data/COM_cfg.txt"); 230 | showSetupTextLine("Port: " + COMport[0] + ", loaded from txt"); 231 | ExportSetupTextLog(); 232 | //showMessageDialog(frame, "COM_cfg.txt found\nDetected port: " + COMport[0], "Info", INFORMATION_MESSAGE); 233 | println("Port: " + COMport[0] + ", loaded from txt"); 234 | myPort = new Serial(this, COMport[0], 115200); 235 | } else { // open window for selecting available COM ports 236 | println("COM: searching..."); 237 | showSetupTextLine("COM_cfg not found, starting setup wizard."); 238 | r = COMselector(); 239 | if (r == 0) { 240 | System.exit(-1); // if errors or Arduino not connected 241 | println("COM: error"); 242 | } else { 243 | String set[] = {Serial.list()[r]}; 244 | saveStrings("/data/COM_cfg.txt", set); //save COM port of Arduino in a file 245 | println("config: saved"); 246 | } 247 | } 248 | myPort.bufferUntil(char(10)); // read serial data utill line feed character (we still need to toss carriage return char from input string) 249 | 250 | font = createFont("Arial", 16, true); 251 | textSize(font_size); 252 | 253 | posY = height - (2.2*axisScale); 254 | 255 | // Create the sprites 256 | Domain domain = new Domain(0, 0, width, height); 257 | sprite[0] = new Sprite(this, "rane_wheel_rim_O-shape.png", 10); 258 | //sprite[0] = new Sprite(this, "rane_wheel_rim_D-shape.png", 10); 259 | //sprite[0] = new Sprite(this, "TX_wheel_rim_small_setupTextInitAlpha.png", 10); 260 | sprite[0].setVelXY(0, 0); 261 | sprite[0].setXY(0.038*width+0.5*axisScale, posY-72); 262 | sprite[0].setDomain(domain, Sprite.REBOUND); 263 | sprite[0].respondToMouse(false); 264 | sprite[0].setZorder(20); 265 | //sprite[0].setScale(0.9); 266 | 267 | //for (int i = 0; i < wheels.length; i++) { 268 | wheels[0] = new Wheel(0.054*width+0.4*axisScale, posY-72, axisScale*0.8, str(frameRate)); 269 | //wheels[1] = new Wheel(width/2+1.8*axisScale, height/2, axisScale*0.9, "LFS car's wheel Y"); 270 | //} 271 | 272 | SetAxisColors(); // checks for existing colors in txt file 273 | 274 | slajderi[0] = new Slajder(axis_color[0], width/3.65 + 0*60, height-posY, 10, 65535, "X", "c", "d", false, false); 275 | slajderi[1] = new Slajder(axis_color[1], width/3.65 + 1*60, height-posY, 10, pdlDefMax, "Y", "a", "b", false, true); 276 | slajderi[2] = new Slajder(axis_color[2], width/3.65 + 2*60, height-posY, 10, pdlDefMax, "Z", "c", "d", false, false); 277 | slajderi[3] = new Slajder(axis_color[3], width/3.65 + 3*60, height-posY, 10, pdlDefMax, "RX", "e", "f", false, false); 278 | slajderi[4] = new Slajder(axis_color[4], width/3.65 + 4*60, height-posY, 10, pdlDefMax, "RY", "g", "h", false, false); 279 | 280 | for (int i=0; i 7 && j < 16) { 289 | dugmici[j] = new Dugme(0.05*width +(j-8)*28, height-posY*1.85+28, 18); 290 | } else if (j > 15 && j < 24) { 291 | dugmici[j] = new Dugme(0.05*width +(j-16)*28, height-posY*1.85+2*28, 18); 292 | } 293 | } 294 | 295 | dialogs[0] = new Dialog(0.05*width, height-posY*1.85+3*28, 16, "waiting input.."); 296 | 297 | // general control push buttons 298 | buttons[1] = new Button(Xoffset+width/2 + 6.35*60, height-posY+140, 50, 16, "default", "load default settings", 0); 299 | buttons[8] = new Button(Xoffset+width/2 + 7.6*60, height-posY+140, 38, 16, "save", "save all settings to arduino", 0); 300 | buttons[9] = new Button(Xoffset+width/2 + 5.3*60, height-posY+140, 38, 16, "pwm", "set and save pwm settings to arduino (arduino reset required)", 0); 301 | buttons[10] = new Button(Xoffset+width/2 + 10.04*60, height-posY+140, 38, 16, "store", "save all settings to PC", 0); 302 | 303 | // info buttons for displaying some settings 304 | String[] enc = new String[2]; 305 | enc[0] = "opt."; // optical quadrature encoder 306 | enc[1] = "mag"; // magnetic encoder 307 | infobuttons[0] = new InfoButton (0.05*width + 3.45*60, height-posY-490, 70, 16, 2, enc, "enc. type", 0); 308 | 309 | // encoder and pedal calibration buttons 310 | buttons[0] = new Button(0.05*width + 3.45*60, height-posY-270, 48, 16, "center", "set to 0°", 0); 311 | buttons[14] = new Button(0.05*width + 4.3*60, height-posY-270, 18, 16, "z", "reset", 0); 312 | buttons[2] = new Button(width/3.7 + 2.9*60, height-posY+31, 70, 16, "auto pcal", "reset", 3); 313 | buttons[13] = new Button(width/3.7 + 2.9*60, height-posY+50, 70, 16, "man. pcal", "set cal", 3); 314 | 315 | // h-shifter buttons 316 | buttons[11] = new Button(width/3.7 + 1.0*60, height-posY+31, 63, 16, "H-shifter", "set cal", 0); 317 | buttons[12] = new Button(width/3.7 + 2.1*60, height-posY+31, 16, 16, "r", "8th", 3); 318 | buttons[15] = new Button(width/3.7 + 1.0*60, height-posY+50, 16, 16, "x", "inv", 2); 319 | buttons[16] = new Button(width/3.7 + 1.3*60, height-posY+50, 16, 16, "y", "inv", 3); 320 | buttons[17] = new Button(width/3.7 + 2.1*60, height-posY+50, 16, 16, "b", "inv", 3); 321 | 322 | // optional and ffb effect on/off toggle buttons 323 | buttons[3] = new Button(sldXoff+width/2+slider_width+60, slider_height/2*(1+8)-12, 16, 16, " ", "autocenter spring", 3); 324 | buttons[4] = new Button(sldXoff+width/2+slider_width+60, slider_height/2*(1+2)-12, 16, 16, " ", "user damper", 3); 325 | buttons[5] = new Button(sldXoff+width/2+slider_width+60, slider_height/2*(1+7)-12, 16, 16, " ", "user inertia", 3); 326 | buttons[6] = new Button(sldXoff+width/2+slider_width+60, slider_height/2*(1+3)-12, 16, 16, " ", "user friction", 3); 327 | buttons[7] = new Button(sldXoff+width/2+slider_width+60, slider_height/2*(1+1)-12, 16, 16, " ", "FFB monitor", 3); 328 | 329 | //keys 330 | keys[0] = "r"; 331 | keys[1] = "c"; 332 | keys[2] = "z"; 333 | keys[3] = "s"; 334 | keys[4] = "p"; 335 | keys[5] = "u"; 336 | keys[6] = "v"; 337 | keys[7] = "d"; 338 | keys[8] = "b"; 339 | keys[9] = "+"; 340 | keys[10] = "-"; 341 | keys[11] = "i"; 342 | 343 | description[0] = "read serial buffer"; 344 | description[1] = "re-center X axis"; 345 | description[2] = "reset encoder Z-index"; 346 | description[3] = "read encoder Z-state"; 347 | description[4] = "reset pedal calibration"; 348 | description[5] = "read firmware settings"; 349 | description[6] = "read frimware version"; 350 | description[7] = "load default settings"; 351 | description[8] = "calibrate right endstop"; 352 | description[9] = "increase rotation (+1deg)"; 353 | description[10] = "decrease rotation (-1deg)"; 354 | description[11] = "show/hide information"; 355 | 356 | for (int n = 0; n < infos.length; n++) { 357 | infos[n] = new Info(0.05*width, height-posY*1.85+4*28+2*n*font_size, font_size, description[n], keys[n]); 358 | } 359 | 360 | for (int k = 0; k < hatsw.length; k++) { 361 | hatsw[k] = new HatSW(0.05*width + 9*28 + 7, height-posY*1.85+1*28 + 10, 14, 48); 362 | } 363 | 364 | sliderlabel[0] = "Rotation [deg]"; 365 | sliderlabel[1] = "General gain [%]"; 366 | sliderlabel[2] = "Damper gain [%]"; 367 | sliderlabel[3] = "Friction gain [%]"; 368 | sliderlabel[4] = "Constant gain [%]"; 369 | sliderlabel[5] = "Periodic gain [%]"; 370 | sliderlabel[6] = "Spring gain [%]"; 371 | sliderlabel[7] = "Inertia gain [%]"; 372 | sliderlabel[8] = "Centering gain [%]"; 373 | sliderlabel[9] = "Stop gain [%]"; 374 | sliderlabel[10] = "Min torque PWM [%]"; 375 | sliderlabel[11] = "Max brake pressure"; 376 | 377 | for (int j = 0; j < sdr.length; j++) { 378 | sdr[j] = new GCustomSlider(this, width/2+sldXoff, slider_height/2*j-4, slider_width, slider_height, "red_yellow18px"); 379 | // Some of the following statements are not actually required because they are setting the default values only 380 | sdr[j].setLocalColorScheme(2); 381 | sdr[j].setOpaque(false); 382 | sdr[j].setNbrTicks(10); 383 | sdr[j].setShowLimits(false); 384 | sdr[j].setShowValue(false); 385 | sdr[j].setShowTicks(true); 386 | sdr[j].setStickToTicks(false); 387 | sdr[j].setEasing(1.0); 388 | sdr[j].setRotation(0.0, GControlMode.CENTER); 389 | } 390 | 391 | // default FFB parameters and firmware settings 392 | defParmFFB[0] = 1080.0; 393 | defParmFFB[1] = 1.0; 394 | defParmFFB[2] = 0.5; 395 | defParmFFB[3] = 0.5; 396 | defParmFFB[4] = 1.0; 397 | defParmFFB[5] = 1.0; 398 | defParmFFB[6] = 1.0; 399 | defParmFFB[7] = 0.5; 400 | defParmFFB[8] = 0.7; 401 | defParmFFB[9] = 1.0; 402 | defParmFFB[10] = 0.0; 403 | defParmFFB[11] = 45.0; 404 | effstatedef = 1; // only autocentering spring is enabled by default 405 | maxTorquedef = 500; 406 | CPRdef = 2400; 407 | pwmstatedef = 12; // fast pwm, 7.8kHz, pwm+- 408 | // default XY shifter calibration values 409 | xysParmDef[0] = 255; 410 | xysParmDef[1] = 511; 411 | xysParmDef[2] = 767; 412 | xysParmDef[3] = 255; 413 | xysParmDef[4] = 511; 414 | xysParmDef[5] = 2; // reverse in 6th gear, x-normal, y-normal, b-normal 415 | 416 | // commands for adjusting FFB parameters 417 | command[0] = "G "; 418 | command[1] = "FG "; 419 | command[2] = "FD "; 420 | command[3] = "FF "; 421 | command[4] = "FC "; 422 | command[5] = "FS "; 423 | command[6] = "FM "; 424 | command[7] = "FI "; 425 | command[8] = "FA "; 426 | command[9] = "FB "; 427 | command[10] = "FJ "; 428 | command[11] = "B "; 429 | // pedal calibration related commands 430 | pdlCommand[0] = "YA "; 431 | pdlCommand[1] = "YB "; 432 | pdlCommand[2] = "YC "; 433 | pdlCommand[3] = "YD "; 434 | pdlCommand[4] = "YE "; 435 | pdlCommand[5] = "YF "; 436 | pdlCommand[6] = "YG "; 437 | pdlCommand[7] = "YH "; 438 | pdlCommand[8] = "YR"; 439 | // XY shifter calibration related commands 440 | shCommand[0] = "HA "; 441 | shCommand[1] = "HB "; 442 | shCommand[2] = "HC "; 443 | shCommand[3] = "HD "; 444 | shCommand[4] = "HE "; 445 | shCommand[5] = "HF "; 446 | shCommand[6] = "HG"; 447 | shCommand[7] = "HR"; 448 | 449 | refreshWheelParm(); // update all wheel FFB parms and close FFB mon if left running 450 | for (int i=0; i < wParmFFB.length; i++) { 451 | setSliderToParm(i); // update sliders with new wheel FFB parms 452 | } 453 | readFwVersion(); // get Arduino FFB Wheel firmware version 454 | 455 | // advanced debugging of firmware options 456 | if (bitRead(fwOpt, 1) == 1) { // if bit1=1 - encoder with Z-index channel suported by firmware (option "z") 457 | showSetupTextLine("Encoder with a Z-index detected"); 458 | println("Encoder Z-index detected"); 459 | } 460 | if (bitRead(fwOpt, 2) == 1) { // if bit2=1 - hat Switch suported by firmware (option "h") 461 | hatsw[0].enabled = true; 462 | for (int i=0; i<4; i++) { 463 | if (!BBenabled && !BMenabled) { // last 4 buttons of 2nd byte are unavailable if we are not using button matrix or button box 464 | dugmici[i].enabled = true; 465 | } 466 | } 467 | showSetupTextLine("Hat switch (D-pad) enabled"); 468 | println("Hat switch enabled"); 469 | } else { 470 | for (int i=0; i<8; i++) { 471 | dugmici[i].enabled = true; // by default we have 8 direct pins for buttons available 472 | } 473 | } 474 | if (bitRead(fwOpt, 3) == 1) { // of bit3=1 - averaging of analog inputs is supported by firmware (option "i") 475 | showSetupTextLine("Averaging of analog inputs for pedals enabled"); 476 | println("Analog input averaging enabled"); 477 | pdlDefMax = 4095; 478 | slajderi[1].am = pdlDefMax; 479 | slajderi[2].am = pdlDefMax; 480 | slajderi[3].am = pdlDefMax; 481 | slajderi[4].am = pdlDefMax; 482 | } else if (bitRead(fwOpt, 4) == 1) { // if bit4=1 - external 12bit ADC ads1105 is supported by firmware (option "s") 483 | showSetupTextLine("External 12bit ADC ads1105 for pedals detected"); 484 | println("ADS1105 detected"); 485 | pdlDefMax = 2047; 486 | slajderi[1].am = pdlDefMax; // brake Y-axis 487 | slajderi[2].am = pdlDefMax; // accelerator Z-axis 488 | slajderi[3].am = pdlDefMax; // clutch RX-axis 489 | slajderi[4].am = pdlDefMax; // handbrake RY-axis 490 | } 491 | 492 | // default pedal calibration settings (depend on firmware options so we need to read fw version first) 493 | for (int i=0; i 11) { // enable first 16 buttons, except for last 4 if we have hat switch 501 | dugmici[i].enabled = false; 502 | } else { 503 | dugmici[i].enabled = true; 504 | } 505 | } 506 | showSetupTextLine("4x4 button matrix detected"); 507 | println("Button matrix detected"); 508 | } 509 | if (BBenabled) { // if button box is supported by firmware, enable first 16 buttons 510 | for (int i=0; i<16; i++) { 511 | if (bitRead(fwOpt, 2) == 1 && i > 11) { // enable first 16 buttons, except for last 4 if we have hat switch 512 | dugmici[i].enabled = false; 513 | } else { 514 | dugmici[i].enabled = true; 515 | } 516 | } 517 | if (bitRead(fwOpt2, 4) == 1) { // if bit4=1, we have firmware with 24 buttons supported (option "r") 518 | for (int i=0; i<8; i++) { 519 | dugmici[16+i].enabled = true; // enable last 8 buttons 520 | } 521 | if (bitRead(fwOpt, 2) == 1) { // if we have hat switch, then only last 4 buttons are unavailable 522 | for (int i=0; i<4; i++) { 523 | dugmici[12+i].enabled = true; // re-enable last 4 buttons in 2nd byte 524 | dugmici[20+i].enabled = false; // disable last 4 buttons in 3rd byte 525 | } 526 | } 527 | showSetupTextLine("Using shift register: SN74ALS166N (24 buttons)"); 528 | println("Nano button box detected"); 529 | } else { // otherwise is 16 buttons 530 | showSetupTextLine("Using shift register: nano button box (16 buttons)"); 531 | println("SN74ALS166N detected"); 532 | } 533 | } 534 | if (bitRead(fwOpt, 5) == 1) { // if bit5=1 - Arduino ProMicro pinouts suported by firmware (option "m") 535 | if (bitRead(fwOpt, 1) == 1 || bitRead(fwOpt, 4) == 1) { 536 | dugmici[3].enabled = false; // button3 is unavailable on proMicro if we use zindex, or any i2C device 537 | } 538 | showSetupTextLine("Arduino ProMicro replacement pinouts detected"); 539 | println("ProMicro pinouts detected"); 540 | } 541 | if (!LCenabled) { // max brake slider becomes FFB balance if no load cell 542 | sliderlabel[11] = "FFB balance L/R"; 543 | defParmFFB[11] = 128.0; 544 | } else { 545 | //slajderi[1].am = 65535; // update bar graph max value for brake axis 546 | showSetupTextLine("Load Cell brake with HX711 detected"); 547 | println("HX711 detected"); 548 | } 549 | if (DACenabled) { 550 | showSetupTextLine("Analog FFB output via MCP4725 DAC detected"); 551 | println("MCP4725 detected"); 552 | sliderlabel[10] = "Min torque DAC [%]"; 553 | } 554 | shifters[0] = new XYshifter(width/3.65-16, height-posY-500, 0.25); 555 | if (XYshifterEnabled) { 556 | //if (!LCenabled) dugmici[3].enabled = false; 557 | if (bitRead(fwOpt, 5) == 1) { // for option "m" in proMicro we have replacement pins for these buttons 558 | dugmici[1].enabled = true; 559 | dugmici[2].enabled = true; 560 | } else { // for leonardo or micro, these buttons are unavailable when XY shifter is used 561 | if (bitRead(fwOpt, 2) == 0) { // if no hat switch on leonardo or micro, we don't have these 4 buttons 562 | for (int i=0; i<4; i++) { 563 | dugmici[4+i].enabled = false; 564 | } 565 | } 566 | } 567 | for (int i=0; i<8; i++) { 568 | dugmici[16+i].enabled = true; // enable last 8 buttons for XY shifter gears 569 | } 570 | showSetupTextLine("Analog XY H-shifter detected"); 571 | println("H-shifter detected"); 572 | buttons[11].active = true; 573 | buttons[12].active = true; 574 | if (fwVerNum >= 230) { // if fw-v230 - h-shifter advanced configuration 575 | buttons[15].active = true; 576 | buttons[16].active = true; 577 | buttons[17].active = true; 578 | } else { 579 | buttons[15].active = false; 580 | buttons[16].active = false; 581 | buttons[17].active = false; 582 | } 583 | refreshXYshifterCal(); // get shifter calibration config from arduino 584 | shifters[0].updateCal(rb); // decode and update shifter cal and config byte values 585 | updateLastShifterConfig(); // get shifter cal values into global variables 586 | showSetupTextLine(rb); 587 | if (bitRead(shifters[0].sConfig, 0) == 1) { // if reverse gear button is inverted 588 | buttonpressed[17] = true; 589 | } else { 590 | buttonpressed[17] = false; 591 | } 592 | if (bitRead(shifters[0].sConfig, 1) == 1) { // if reverse gear in 8th 593 | buttonpressed[12] = true; 594 | } else { 595 | buttonpressed[12] = false; 596 | } 597 | if (bitRead(shifters[0].sConfig, 2) == 1) { // if shifter X-axis is inverted 598 | buttonpressed[15] = true; 599 | } else { 600 | buttonpressed[15] = false; 601 | } 602 | if (bitRead(shifters[0].sConfig, 3) == 1) { // if shifter Y-axis is inverted 603 | buttonpressed[16] = true; 604 | } else { 605 | buttonpressed[16] = false; 606 | } 607 | } else { 608 | buttons[11].active = false; 609 | buttons[12].active = false; 610 | buttons[15].active = false; 611 | buttons[16].active = false; 612 | buttons[17].active = false; 613 | } 614 | if (bitRead(fwOpt2, 0) == 1) { // if bit0=1 - extra buttons suported by firmware (option "e") 615 | // we have 2 extra buttons instead of clutch and handbrake 616 | if (bitRead(fwOpt, 2) == 1) { // if option "h" then buttons 4,5 are available 617 | dugmici[4].enabled = true; 618 | dugmici[5].enabled = true; 619 | } else { 620 | dugmici[8].enabled = true; // else remaped to buttons 8,9 621 | dugmici[9].enabled = true; 622 | } 623 | showSetupTextLine("Two extra buttons detected"); 624 | println("Extra buttons detected"); 625 | } 626 | if (bitRead(fwOpt2, 1) == 1) { // if bit1=1 - analog axis for FFB suported by firmware (option "x") 627 | showSetupTextLine("Analog axis for FFB enabled"); 628 | println("Analog axis for FFB enabled"); 629 | } 630 | if (bitRead(fwOpt2, 2) == 1) { // if bit2=1 - magnetic angle sensor AS5600 suported by firmware (option "w") 631 | if (bitRead(fwOpt, 5) == 1) { // if ProMicro 632 | if (bitRead(fwOpt, 2) == 1) { // if hat switch 633 | dugmici[3].enabled = false; 634 | } 635 | } 636 | infobuttons[0].as = 1; // set magnetic encoder in info button 637 | noMagEnc = false; 638 | showSetupTextLine("AS5600 magnetic encoder detected"); 639 | println("AS5600 detected"); 640 | } else { 641 | noMagEnc = true; 642 | infobuttons[0].as = 0; // set optical quadrature encoder in info button 643 | } 644 | if (bitRead(fwOpt3, 3) == 1) { // if bit3=1 - tca9548 multiplexer is suported by firmware (option "u") 645 | TCA9548enabled = true; 646 | showSetupTextLine("TCA9548A i2C multiplexer detected"); 647 | println("TCA9548A i2C multiplexer detected"); 648 | } 649 | if (bitRead(fwOpt2, 3) == 1) { // if bit3=1 - hardware re-center is suported by firmware (option "c") 650 | showSetupTextLine("Hardware re-center button enabled"); 651 | println("Re-center button enabled"); 652 | } 653 | if (bitRead(fwOpt2, 6) == 1) { // if bit6=1 - no optical encoder is suported by firmware (option "d") 654 | noOptEnc = true; 655 | showSetupTextLine("No optical encoder supported"); 656 | println("No optical encoder supported"); 657 | } else { 658 | noOptEnc = false; 659 | } 660 | if (bitRead(fwOpt2, 5) == 0) { // if bit5=0, only 1 FFB axis is supported by firmware 661 | twoFFBaxis_enabled = false; 662 | ffbgraphs[0] = new FFBgraph(width, height-gbuffer/gskip, width, 1); // FFB graph for X-axis 663 | } else { // if bit5=0, only 2 FFB axis are supported by firmware (split-view FFB monitor graphs) 664 | String out = "PWM"; 665 | if (DACenabled) out = "DAC"; 666 | showSetupTextLine("2 FFB axis and 2 channel " +out+ " output enabled"); 667 | println("2 FFB axis detected"); 668 | twoFFBaxis_enabled = true; 669 | dugmici[7].enabled = false; 670 | gbuffer = 250; // half the graph data points 671 | ffbgraphs[0] = new FFBgraph(width, height-gbuffer/gskip, width, 1); // FFB graph for X-axis 672 | ffbgraphs[1] = new FFBgraph(width, height-2*(gbuffer/gskip), width, 1); // FFB graph for Y-axis 673 | } 674 | if (noOptEnc && noMagEnc) { 675 | infobuttons[0].as = -1; // deactivate both options in info button 676 | slajderi[2].yLimits[0].active = false; // disable cal limits on Z-axis 677 | slajderi[2].yLimits[1].active = false; 678 | slajderi[2].inactive = true; // inactivate Z-axis 679 | } 680 | if (fwVerNum >= 200) { // if =>fw-v200 - we have additional firmware options 681 | if (LCenabled) { 682 | slajderi[1].yLimits[0].active = false; // if load cell, inactivate manual cal for brake axis 683 | slajderi[1].yLimits[1].active = false; 684 | } 685 | if (bitRead(fwOpt2, 0) == 1) { // if bit0 of firmware options byte2 is HIGH, we have extra buttons and no clutch and handbrake 686 | if (!LCenabled) { 687 | slajderi[3].yLimits[0].active = false; // only if no load cell, inactivate manual cal for clutch axis 688 | slajderi[3].yLimits[1].active = false; 689 | } 690 | slajderi[4].yLimits[0].active = false; // if extra buttons, inactivate manual cal for handbrake axis 691 | slajderi[4].yLimits[1].active = false; 692 | } 693 | if (bitRead(fwOpt, 7) == 1 && bitRead(fwOpt, 5) == 1) { // if options "f" and "m" clutch and hbrake axis are unavailable 694 | slajderi[3].yLimits[0].active = false; 695 | slajderi[3].yLimits[1].active = false; 696 | slajderi[4].yLimits[0].active = false; 697 | slajderi[4].yLimits[1].active = false; 698 | } 699 | if (bitRead(fwOpt2, 1) == 1) { // if bit1 of firmware options byte2 is HIGH, we have available FFB axis selector 700 | AFFBenabled = true; 701 | } 702 | if (bitRead(fwOpt, 0) == 0) { // if bit0=0 - pedal autocalibration is disabled, then we have manual pedal calibration 703 | println("Manual pcal enabled"); 704 | refreshPedalCalibration(); 705 | updateLastPedalCalibration(rb); 706 | if (LCenabled) { 707 | slajderi[1].am = 65535; // update bar graph max value for brake axis to full 16bit resolution 708 | } 709 | showSetupTextLine("Manual calibration for pedals enabled"); 710 | showSetupTextLine(rb); 711 | } else { 712 | showSetupTextLine("Automatic calibration for pedals enabled"); 713 | println("Automatic pcal detected"); 714 | buttons[13].active = false; // disable manual cal button if pedal auto calib firmware 715 | } 716 | if (bitRead(fwOpt, 1) == 1) { // if bit1=1, encoder z-index is supported by firmware 717 | buttons[14].active = true; // activate z-reset button 718 | } else { 719 | buttons[14].active = false; // inactivate z-reset button 720 | } 721 | } 722 | if (bitRead(fwOpt2, 7) == 1) { // if bit7 of firmware options byte2 is HIGH, EEPROM is not used in firmware 723 | showSetupTextLine("EEPROM is not used"); 724 | println("EEPROM is not used"); 725 | } else { 726 | showSetupTextLine("Using EEPROM to load/save settings"); 727 | println("Using EEPROM to load/save settings"); 728 | } 729 | if (fwVerNum >= 240) { // in firmware v240 we are in-activating unavailable buttons, some buttons are re-mapped (just visual fix) 730 | dActByp = false; // do not bypass button in-activation 731 | infobuttons[0].hiden = false; // un-hide the encoder type info button 732 | } 733 | wb = "V"; 734 | executeWR(); 735 | 736 | // create number box object for CPR adjustment 737 | cp5 = new ControlP5(this); 738 | num1 = cp5.addNumberbox("CPR") 739 | .setSize(45, 18) 740 | .setPosition(int(width/3.65) - 15 + 0.0*60, height-posY+30) 741 | .setValue(lastCPR) 742 | .setRange(0, maxCPR) 743 | ; 744 | makeEditable(num1); 745 | 746 | // create scrolable list object for PWM adjustment 747 | cp5 = new ControlP5(this); 748 | List b = Arrays.asList("fast pwm", "phase corr"); 749 | List c = Arrays.asList("pwm +-", "pwm+dir"); 750 | List c1 = Arrays.asList("pwm +-", "pwm+dir", "pwm0-50-100"); 751 | List c2_rcm = Arrays.asList("pwm +-", "pwm+dir", "pwm0-50-100", "rcm"); 752 | List c3_2pwm = Arrays.asList("2ch pwm +-", "2ch pwm+dir", "2ch pwm0-50-100", "2ch rcm"); 753 | List d = Arrays.asList("dac +-", "dac+dir"); 754 | List d_250 = Arrays.asList("dac +-", "dac+dir", "dac0-50-100"); 755 | List d_250_2ch = Arrays.asList("1ch dac +- (xFFB)", "2ch dac+dir", "2ch dac0-50-100"); 756 | List d2 = Arrays.asList("dac off", "dac on"); 757 | List e = Arrays.asList("default"); 758 | /* add a ScrollableList, by default it behaves like a DropdownList */ 759 | cp5.addScrollableList("profile") 760 | .setPosition(Xoffset+int(width/3.5) - 15 + 14*60, height-posY+30+108) 761 | .setSize(66, 100) 762 | .setBarHeight(20) 763 | .setItemHeight(20) 764 | .addItems(e) 765 | ; 766 | cp5.get(ScrollableList.class, "profile").close(); 767 | float[] def = new float[num_prfset]; 768 | for (int i =0; i= 250 && !twoFFBaxis_enabled) { 853 | cp5.get(ScrollableList.class, "dacmode").removeItems(d); 854 | cp5.get(ScrollableList.class, "dacmode").addItems(d_250); 855 | cp5.get(ScrollableList.class, "dacmode").setSize(74, 100); 856 | cp5.get(ScrollableList.class, "dacmode").setPosition(Xoffset+int(width/3.5) - 20 + 9.2*60, height-posY+30+108); 857 | } else if (fwVerNum >= 250 && twoFFBaxis_enabled) { 858 | cp5.get(ScrollableList.class, "dacmode").removeItems(d); 859 | cp5.get(ScrollableList.class, "dacmode").addItems(d_250_2ch); 860 | cp5.get(ScrollableList.class, "dacmode").setSize(90, 100); 861 | cp5.get(ScrollableList.class, "dacmode").setPosition(Xoffset+int(width/3.5) - 20 + 9.0*60, height-posY+30+108); 862 | } 863 | cp5.get(ScrollableList.class, "dacmode").close(); 864 | cp5.get(ScrollableList.class, "dacmode").setValue(int(modedac)); 865 | cp5.addScrollableList("dacout") 866 | .setPosition(Xoffset+int(width/3.5) - 5 + 7.6*60, height-posY+30+108) 867 | .setSize(53, 100) 868 | .setBarHeight(20) 869 | .setItemHeight(20) 870 | .addItems(d2) 871 | //.setType(ScrollableList.LIST) // currently supported DROPDOWN and LIST 872 | ; 873 | if (!twoFFBaxis_enabled) cp5.get(ScrollableList.class, "dacout").setPosition(Xoffset+int(width/3.5) + 4 + 7.6*60, height-posY+30+108); 874 | cp5.get(ScrollableList.class, "dacout").close(); 875 | cp5.get(ScrollableList.class, "dacout").setValue(int(enabledac)); 876 | } 877 | if (AFFBenabled) { // if firmware supports analog FFB axis 878 | String xm = "enc"; 879 | if (noOptEnc && noMagEnc) xm = "pot"; // x-axis is on analog input, potentiometer 880 | List fb = Arrays.asList("x-"+xm, "y-brk", "z-acc", "rx-clt", "ry-hbr"); 881 | if (bitRead(fwOpt, 5) == 1 && bitRead(fwOpt, 7) == 1) fb = Arrays.asList("x-"+xm, "y-brk", "z-acc"); // if "f" and "m" options, we don't have clutch and hbrake axis available 882 | cp5.addScrollableList("xFFBaxis") 883 | .setPosition(Xoffset+int(width/3.5) - 19 - 0.85*60, height-posY+5) 884 | .setSize(60, 100) 885 | .setBarHeight(20) 886 | .setItemHeight(20) 887 | .addItems(fb) 888 | //.setType(ScrollableList.DROPDOWN) // currently supported DROPDOWN and LIST 889 | ; 890 | cp5.get(ScrollableList.class, "xFFBaxis").close(); 891 | cp5.get(ScrollableList.class, "xFFBaxis").setValue(int(xFFBAxisIndex)); 892 | } 893 | loadProfiles(); // check if exists and load profiles from txt 894 | showSetupTextLine("Configuration done"); 895 | ExportSetupTextLog(); 896 | setupTextAlpha = setupTextInitAlpha; 897 | } 898 | 899 | void draw() { 900 | background(51); 901 | drawGUI(); 902 | } 903 | 904 | void drawGUI() { 905 | draw_labels(); 906 | for (int i = 0; i < slajderi.length; i++) { 907 | slajderi[i].update(i); 908 | slajderi[i].show(); 909 | } 910 | for (int j = 0; j < dugmici.length; j++) { 911 | dugmici[j].update(); 912 | buttonValue = Button[j]; 913 | dugmici[j].show(j); 914 | } 915 | for (int k = 0; k < hatsw.length; k++) { 916 | hatsw[k].update(); 917 | hatsw[k].show(); 918 | hatsw[k].showArrow(); 919 | } 920 | if (!twoFFBaxis_enabled) { 921 | // my simple animated wheel gfx 922 | wheels[0].updateWheel(slajderi[xFFBAxisIndex].axisVal*wParmFFB[0]/2); // update the angle in units of degrees 923 | wheels[0].showWheelDeg(); // show the angle in units of degrees in a nice number format 924 | //wheels[0].showWheel(); 925 | if (!buttonpressed[7]) { 926 | S4P.updateSprites(1); // animated wheel from png sprite 927 | sprite[0].setRot(slajderi[xFFBAxisIndex].axisVal*wParmFFB[0]/2/180*PI); // set the angle of the sprite in units of radians 928 | S4P.drawSprites(); 929 | } 930 | } else { 931 | wheels[0].updateJoy(slajderi[xFFBAxisIndex].axisVal, slajderi[1].axisVal); // xFFB on X-axis, yFFB on Y-axis 932 | wheels[0].showJoy(); 933 | wheels[0].showJoyPos(); 934 | } 935 | for (int j = 0; j < infobuttons.length; j++) { 936 | infobuttons[j].update(); 937 | infobuttons[j].show(); 938 | } 939 | for (int k = 0; k < buttons.length; k++) { 940 | buttons[k].update(k); 941 | buttons[k].show(); 942 | } 943 | for (int l = 0; l < infos.length; l++) { 944 | infos[l].show(enableinfo); 945 | } 946 | if (buttonpressed[7]) { 947 | int gmult = 1; 948 | if (twoFFBaxis_enabled) gmult = 2; 949 | for (int i=0; i 1) { 955 | ffbx = val[0]; // X-axis FFB data 956 | ffby = val[1]; // Y-axis FFB data 957 | ffbgraphs[0].update(str(ffbx)); 958 | ffbgraphs[1].update(str(ffby)); 959 | } 960 | } else { 961 | ffbgraphs[0].update(temprb); // X-axis FFB data 962 | } 963 | } 964 | } 965 | ffbgraphs[0].show(0); // X-axis FFB data 966 | if (twoFFBaxis_enabled) ffbgraphs[1].show(1); // Y-axis FFB data 967 | } else { 968 | if (fbmnstp) { // read remaining serial read buffer content 969 | String temprb = ""; 970 | while (readString() != "empty") { 971 | temprb = rb; 972 | } 973 | rb = temprb; // restore read buffer 974 | fbmnstp = false; 975 | } 976 | } 977 | if (buttonpressed[7]) { 978 | dialogs[0].update("WB: "+ wb + ", RB: " + fbmnstring + "; " + str(rbt_ms) + "ms"); 979 | } else { 980 | dialogs[0].update("WB: "+ wb + ", RB: " + rb + "; " + str(rbt_ms) + "ms"); 981 | } 982 | dialogs[0].show(); 983 | text(round(frameRate)+" fps", font_size/3, font_size); 984 | if (CPRlimit) { 985 | num1.setValue(maxAllowedCPR(wParmFFBprev[0])); 986 | CPRlimit = false; 987 | } 988 | if (!buttonpressed[7]) { // only available if ffb monitor is not enabled 989 | if (XYshifterEnabled) buttons[11].active = true; // re-enable only if firmware supports it 990 | if (XYshifterEnabled) buttons[12].active = true; 991 | if (buttonpressed[11]) { // if we pressed shifter button 992 | refreshXYshifterPos(); // get new shifter XY position from arduino 993 | shifters[0].updatePos(); // update new shifter XY position 994 | shifters[0].setCal(); // actuate cal sliders and update limits 995 | shifters[0].show(); // display shifter - cal sliders, limits and xy shifter position 996 | buttons[7].active = false; // disable ffb monitor while we configure XY shifter 997 | } else { 998 | buttons[7].active = true; // re-enable ffb monitor if we are not configuring XY shifter 999 | } 1000 | } else { 1001 | buttons[11].active = false; // disable XY shifter button while we are running ffb monitor 1002 | //buttons[12].active = false; // disable shifter r button while we are running ffb monitor 1003 | } 1004 | if (noOptEnc && noMagEnc) buttons[0].active = false; // disable center button if we have no digital encoders 1005 | if (twoFFBaxis_enabled) buttons[0].d = "set x to 0%"; 1006 | if (twoFFBaxis_enabled && TCA9548enabled && !noMagEnc) { 1007 | buttons[0].d = "set x,y to 0%"; 1008 | slajderi[1].am = 65535; 1009 | } 1010 | if (bitRead(fwOpt, 1) == 1) buttons[14].active = true; // re-enable z button if supported by firmware 1011 | if (showSetupText) { 1012 | animateSetupTextBuffer(frameCount); 1013 | } 1014 | } 1015 | 1016 | void draw_labels() { 1017 | String labelStr; 1018 | fill(255); 1019 | for (int j = 0; j < sdr.length; j++) { // FFB slider values 1020 | labelStr = str(slider_value[j]); 1021 | if (j == 0 || j == 11) { // only for rotation and brake pressure 1022 | labelStr=labelStr.substring(0, labelStr.length()-2); // do not show decimal places 1023 | if (!LCenabled && j == 11) labelStr = str(slider_value[j]-128).substring(0, (str(slider_value[j]-128)).length()-2); // shift the value such that center iz at zero 1024 | } else if (j == 10) { // only for min PWM 1025 | if (slider_value[j] < 10.0 ) { 1026 | labelStr=labelStr.substring(0, 3); 1027 | } else { 1028 | labelStr=labelStr.substring(0, 4); 1029 | } 1030 | } else { 1031 | labelStr = str(ceil(slider_value[j]*100)); // fix, show 100% instead of 1.0% 1032 | /*if (slider_value[j]*100 >= 100 ) { 1033 | labelStr=labelStr.substring(0, 3); 1034 | } else { 1035 | if (labelStr.length() >= 4 ) { 1036 | labelStr=labelStr.substring(0, 4); 1037 | } 1038 | }*/ 1039 | } 1040 | //textMode(TOP); 1041 | text(sliderlabel[j], sldXoff+width/2-slider_width/3, slider_height/2*(1+j)); // slider label 1042 | text(labelStr, sldXoff+width/2+slider_width+20, slider_height/2*(1+j)); // slider value 1043 | } 1044 | if (AFFBenabled) { 1045 | text("xFFB-axis", Xoffset+int(width/3.5) - 18 - 0.85*60, height-posY+2); // xFFB axis selector label 1046 | } 1047 | // about info display 1048 | pushMatrix(); 1049 | translate(width/3.5, height-159); 1050 | text("Arduino FFB Wheel, HEX " + FullfwVerStr.substring(3, FullfwVerStr.length()), 0, 0); 1051 | text("Control Panel " + cpVer, 0, 20); 1052 | text("Miloš Ranković 2018-2025 ©", 0, 40); 1053 | text("ranenbg@gmail.com, paypal@ranenbg.com", 0, 60); 1054 | popMatrix(); 1055 | } 1056 | 1057 | void writeString(String input) { 1058 | myPort.write(input+char(13)); // add CR - carage return as output line terminator 1059 | } 1060 | 1061 | String readString() { 1062 | if (myPort.available() > 0) { // if serial port data is available 1063 | rb = myPort.readStringUntil(char(10)); // read till terminator char - LF (line feed) and store it in rb 1064 | if (rb != null) { // if there is something in rb 1065 | rb = rb.substring(0, (rb.indexOf(char(13)))); // remove last 2 chars - Arduino sends both CR+LF, char(13)+char(10) 1066 | } else { 1067 | rb = "empty"; 1068 | } 1069 | } else { 1070 | rb = "empty"; 1071 | } 1072 | return rb; 1073 | } 1074 | 1075 | void executeWR() { 1076 | rbt_ms = 0; 1077 | writeString(wb); 1078 | //delay(21); // no longer needed since improved read functions 1079 | // serial read period I've set in arduino is every 10ms 1080 | for (int i = 0; i <=9999; i++) { // but just in case (calibration), we give arduino more time up to 10s 1081 | if (readString() == "empty") { 1082 | rbt_ms++; 1083 | delay(1); 1084 | } else { 1085 | break; 1086 | } 1087 | } 1088 | fbmnstring = rb; 1089 | println("WB:"+ wb + ", RB:" + rb + "; " + str(rbt_ms) + "ms"); 1090 | } 1091 | 1092 | void refreshWheelParm() { 1093 | myPort.clear(); 1094 | writeString("U"); 1095 | if (UpdateWparms(readParmUntillEmpty())) { 1096 | showSetupTextLine("Firmware settings detected"); 1097 | showSetupTextLine(rb); 1098 | println("Firmware settings detected"); 1099 | } else { 1100 | showSetupTextLine("Incompatible firmware settings"); 1101 | println("Incompatible firmware settings"); 1102 | } 1103 | if (bitRead(effstate, 4) == 1) { // if FFB mon is running 1104 | showSetupTextLine("De-activating FFB monitor"); 1105 | println("De-activating FFB monitor"); 1106 | effstate = bitWrite(effstate, 4, false); // turn it OFF 1107 | sendEffstate(); // send command to Arduino 1108 | readParmUntillEmpty(); // read remaining FFB mon data 1109 | } 1110 | for (int i=0; i < wParmFFB.length; i++) { 1111 | wParmFFBprev[i] = wParmFFB[i]; 1112 | print(wParmFFB[i]); 1113 | print(" "); 1114 | } 1115 | readEffstate(); 1116 | readPWMstate(); 1117 | print(int(effstate)); 1118 | //print(" "); 1119 | //print("read: " + int(xFFBAxisIndex)); 1120 | print(" "); 1121 | print(maxTorque); 1122 | print(" "); 1123 | print(lastCPR); 1124 | print(" "); 1125 | print(int(pwmstate)); 1126 | println("; " + str(rbt_ms) + "ms"); 1127 | } 1128 | 1129 | String readParmUntillEmpty() { // reads serial buffer untill empty and returns a string longer than 11 chars (non-FFB monitor data) 1130 | String buffer = ""; 1131 | String temp = ""; 1132 | rbt_ms = 0; 1133 | for (int i=0; i<999; i++) { 1134 | temp = readString(); 1135 | if (temp != "empty") { 1136 | if (temp.length() > 11) { 1137 | buffer = temp; 1138 | break; 1139 | } 1140 | } else { 1141 | rbt_ms++; 1142 | delay(1); 1143 | } 1144 | } 1145 | return buffer; 1146 | } 1147 | 1148 | boolean UpdateWparms(String input) { // decode wheel parameters into FFB, CPR and PWM settings and returns false if format is incorrect 1149 | boolean correct; 1150 | float[] temp = float(split(input, ' ')); 1151 | if (temp.length > 0) { 1152 | correct = true; 1153 | } else { 1154 | correct = false; 1155 | } 1156 | for (int i=0; i < temp.length; i++) { 1157 | if (i == 0) { 1158 | wParmFFB[0] = temp[0]; 1159 | } else { 1160 | if (i < temp.length-4) { // for all but last 4 values 1161 | if (i != 10 && i != 11) { //all except for min torque and brake pressure 1162 | wParmFFB[i] = temp[i] / 100.0; 1163 | } else { 1164 | wParmFFB[i] = temp[i]; 1165 | } 1166 | } else if (i == temp.length-4) { // this value is effstate in binary form 1167 | effstate = byte(temp[i]); 1168 | } else if (i == temp.length-3) { // this value is max torque or PWM resolution per channel 1169 | maxTorque = int(temp[i]); 1170 | } else if (i == temp.length-2) { // this value is encoder CPR 1171 | lastCPR = int(temp[i]); 1172 | } else if (i == temp.length-1) { // last value is pwmstate in binary form 1173 | pwmstate = byte(temp[i]); 1174 | } 1175 | } 1176 | } 1177 | wParmFFB[10] = wParmFFB[10] / float(maxTorque) * 100.0; // recalculate to percentage min pwm value 1178 | return correct; 1179 | } 1180 | 1181 | void readFwVersion() { // reads firmware version from String, checks and updates all firmware options 1182 | myPort.clear(); 1183 | println("Reading firmware version"); 1184 | wb = "V"; 1185 | executeWR(); 1186 | FullfwVerStr = rb; 1187 | showSetupTextLine("HEX version: " + FullfwVerStr); 1188 | fwVerStr = rb.substring(4); // first 4 chars are allways "fw-v", followed by 3 numbers 1189 | String fwTemp = fwVerStr; 1190 | String fwOpts = "0"; 1191 | if (fwTemp.length() > 3) { // if there are any firmware options present 1192 | fwVerStr = fwTemp.substring(0, 3); // remove everything after numbers 1193 | fwOpts = fwTemp.substring(3, fwTemp.length()); // remove only numbers 1194 | } 1195 | fwVerNum = parseInt(fwVerStr); 1196 | fwOpt = decodeFwOpts(fwOpts); // decode fw options into 1st byte 1197 | fwOpt2 = decodeFwOpts2(fwOpts); // decode fw options into 2nd byte 1198 | fwOpt3 = decodeFwOpts3(fwOpts); // decode fw options into 2nd byte 1199 | String fwVerNumStr = str(fwVerNum); 1200 | int len = fwVerNumStr.length(); 1201 | if (fwVerNum < 250) { // for all firmware prior to fw-v250 1202 | if (fwVerNumStr.charAt(len-1) == '0' || fwVerNumStr.charAt(len-1) == '1') { // if last number is 0 or 1 1203 | LCenabled = false; // we don't have load cell or dac 1204 | DACenabled = false; 1205 | } 1206 | if (fwVerNumStr.charAt(len-1) == '1') { // if last number is 1 1207 | BBenabled = true; 1208 | } 1209 | if (fwVerNumStr.charAt(len-1) == '2') { // if last number is 2 1210 | BBenabled = true; // we have button box 1211 | LCenabled = true; // we have load cell 1212 | } 1213 | if (fwVerNumStr.charAt(len-1) == '3') { // if last number is 3 1214 | LCenabled = true; // we have load cell and dac 1215 | DACenabled = true; 1216 | } 1217 | } else { // for fw-v250 and onward I have changed how we interpret firmware version - load cell, button box and dac are firmware options now 1218 | if (bitRead(fwOpt3, 0) == 1) { // if bit0 is HIGH (option "n" - button box) 1219 | BBenabled = true; 1220 | } else { 1221 | BBenabled = false; 1222 | } 1223 | if (bitRead(fwOpt3, 1) == 1) { // if bit1 is HIGH (option "l" - load cell) 1224 | LCenabled = true; 1225 | } else { 1226 | LCenabled = false; 1227 | } 1228 | if (bitRead(fwOpt3, 2) == 1) { // if bit2 is HIGH (option "g" - external dac) 1229 | DACenabled = true; 1230 | } else { 1231 | DACenabled = false; 1232 | } 1233 | } 1234 | if (bitRead(fwOpt, 7) == 1) { // if bit7 is HIGH (xy shifter bit) 1235 | XYshifterEnabled = true; 1236 | } 1237 | if (bitRead(fwOpt, 0) == 0) buttons[2].active = false; // if bit0 is LOW (no pedal autocalibration available) 1238 | if (fwVerNum >= 200 && fwVerNum < 210) pwm0_50_100enabled = true; 1239 | if (fwVerNum >= 210) RCMenabled = true; 1240 | if (bitRead(fwOpt2, 0) == 1) { // if bit0 is HIGH (clutch and handbrake are unavailable) 1241 | clutchenabled = false; 1242 | hbrakeenabled = false; 1243 | } 1244 | if (bitRead(fwOpt, 6) == 1) { // if bit6=1 of fw opt 1st byte 1245 | BMenabled = true; // we have button matrix available 1246 | } 1247 | } 1248 | // decode 1st byte of firmware options 1249 | byte decodeFwOpts (String fopt) { 1250 | byte temp = 0; 1251 | if (fopt != "0") { // if firmware has any options 1252 | for (int j=0; j= 0.0) { 1758 | wParmFFB[0] += abs(step); 1759 | if (wParmFFB[0] >= maxAllowedDeg(lastCPR)) { 1760 | wParmFFB[0] = round(maxAllowedDeg(lastCPR)); 1761 | } 1762 | } else { 1763 | wParmFFB[0] -= abs(step); 1764 | if (wParmFFB[0] <= deg_min) { 1765 | wParmFFB[0] = deg_min; 1766 | } 1767 | } 1768 | sdr[0].setValue(wParmFFB[0]/deg_max); 1769 | slider_value[0] = wParmFFB[0]; 1770 | wb = command[0] + str(int(wParmFFB[0])); 1771 | } 1772 | 1773 | void ActuateButton(int id) { // turns specific button on/off 1774 | if (!buttonpressed[id]) { 1775 | buttonpressed[id] = true; 1776 | } else { 1777 | buttonpressed[id] = false; 1778 | if (id == 7) { // if ffb monitor button de-activated 1779 | fbmnstp = true; 1780 | } 1781 | } 1782 | } 1783 | 1784 | int bitRead(byte b, int bitPos) { // found this on net, now it is the same as bitRead in arduino 1785 | int x = b & (1 << bitPos); 1786 | return x == 0 ? 0 : 1; 1787 | } 1788 | 1789 | byte bitWrite(byte register, int bitPos, boolean value) { // arduino's analog of bitWrite 1790 | if (value) { // turn bit on at bitPos 1791 | register |= 1 << bitPos; 1792 | } else { // turn bit off at bitPos 1793 | register &= ~(1 << bitPos); 1794 | } 1795 | return register; 1796 | } 1797 | 1798 | void readEffstate () { // decode effstate byte 1799 | for (int j=0; j<=4; j++) { 1800 | buttonpressed[j+3] = boolean(bitRead(effstate, j)); // decode desktop user effect switches 1801 | } 1802 | for (int j=5; j<=7; j++) { 1803 | xFFBAxisIndex = bitWrite(byte(xFFBAxisIndex), j-5, boolean(bitRead(effstate, j))); // decode FFB axis index 1804 | } 1805 | effstateprev = effstate; 1806 | } 1807 | 1808 | void sendEffstate () { // send effstate byte to arduino 1809 | wb = "E " + str(int(effstate)); // set command for switches 1810 | executeWR(); // send switch values to arduino and read buffer from wheel 1811 | } 1812 | 1813 | void updateEffstate () { // code settings into effstate byte 1814 | for (int k=0; k <=4; k++) { //needs to be k<=4 here 1815 | effstate = bitWrite(effstate, k, buttonpressed[k+3]); // code control switches 1816 | } 1817 | for (int k=5; k <=7; k++) { 1818 | effstate = bitWrite(effstate, k, boolean(bitRead(byte(xFFBAxisIndex), k-5))); // code FFB axis index 1819 | } 1820 | } 1821 | 1822 | void readPWMstate () { // decode settings from pwmstate value 1823 | typepwm = boolean(bitRead (pwmstate, 0)); // bit0 of pwmstate is pwm type 1824 | // put bit1 of pwmstate to bit0 modepwm 1825 | modepwm = bitWrite(byte(modepwm), 0, boolean(bitRead (pwmstate, 1))); // bit1 and bit6 of pwmstate contain pwm mode (here we take care of bit1) 1826 | 1827 | // pwmstate bits meaning when no DAC 1828 | // bit1 bit6 pwm_mode pwm_mode2 (2-ffb axis) 1829 | // 0 0 pwm+- 2ch pwm+- 1830 | // 0 1 pwm0.50.100 2ch pwm0.50.100 (not available yet) 1831 | // 1 0 pwm+dir 2ch pwm+dir (not available yet) 1832 | // 1 1 rcm 2ch rcm (not available yet) 1833 | 1834 | for (int i=2; i<=5; i++) { // read frequency index, bits 2-5 of pwmstate 1835 | freqpwm = bitWrite(byte(freqpwm), i-2, boolean(bitRead(pwmstate, i))); 1836 | } 1837 | 1838 | // pwmstate bits meaning with DAC 1839 | // bit6 bit5 dac_mode dac_mode2 (2-ffb axis) 1840 | // 0 0 dac+- 1ch dac+- 1841 | // 0 1 dac0.50.100 2ch dac0.50.100 1842 | // 1 0 dac+dir 2ch dac+dir 1843 | // 1 1 none none 1844 | 1845 | // modedac 1846 | // bit0 bit1 dac_mode 1847 | // 0 0 dac+- 1848 | // 0 1 dac+dir 1849 | // 1 0 dac0.50.100 1850 | // 1 1 none 1851 | 1852 | // bit6 and bit5 of pwmstate contain DAC mode 1853 | modedac = bitWrite(byte(modedac), 0, boolean(bitRead(pwmstate, 6))); // put bit6 of pwmstate to bit0 of modedac 1854 | modedac = bitWrite(byte(modedac), 1, boolean(bitRead(pwmstate, 5))); // put bit5 of pwmstate to bit1 of modedac 1855 | // put bit6 of pwmstate to bit1 of modepwm 1856 | modepwm = bitWrite(byte(modepwm), 1, boolean(bitRead(pwmstate, 6))); // bit1 and bit6 of pwmstate contain pwm mode (here we take care of bit6) 1857 | enabledac = boolean(bitRead(pwmstate, 7)); // bit7 of pwmstate is DAC out enable 1858 | pwmstateprev = pwmstate; 1859 | } 1860 | 1861 | void sendPWMstate () { // send pwmstate value to arduino 1862 | boolean settingAllowed = false; 1863 | // check if selected pwm freq in RCM mode is allowed 1864 | if (!RCMenabled) { // for older fw version all pwm modes are available 1865 | settingAllowed = true; 1866 | } else { // for RCM fw-v210 or above 1867 | if (RCMselected) { // if we selected RCM mode 1868 | if (freqpwm >= allowedRCMfreqID) settingAllowed = true; // if freq is lower or equal than 500Hz, freq ID higher or equal 4 1869 | } else { // if we selected any other pwm mode 1870 | settingAllowed = true; 1871 | } 1872 | } 1873 | if (settingAllowed) { 1874 | wb = "W " + str(int(pwmstate)); // set command for pwm settings 1875 | executeWR(); // send values to arduino and read response from it (arduino will save it in EEPPROM right away) 1876 | } else { 1877 | showMessageDialog(frame, "You are trying to set pwm/dac settings that are not allowed,\nplease set correct pwm/dac settings first and try again.", "Caution", WARNING_MESSAGE); 1878 | } 1879 | } 1880 | 1881 | void updatePWMstate () { // code pwmstate byte from pwm/dac setting values 1882 | pwmstate = bitWrite(pwmstate, 0, typepwm); 1883 | pwmstate = bitWrite(pwmstate, 1, boolean(bitRead(byte(modepwm), 0))); // copy bit0 of modepwm into bit1 of pwmstate 1884 | for (int i=2; i <=5; i++) { // set frequency index, bits 2-5 of pwmstate 1885 | pwmstate = bitWrite(pwmstate, i, boolean(bitRead(byte(freqpwm), i-2))); 1886 | } 1887 | if (DACenabled) { 1888 | pwmstate = bitWrite(pwmstate, 6, boolean(bitRead(byte(modedac), 0))); 1889 | if (fwVerNum >= 250) pwmstate = bitWrite(pwmstate, 5, boolean(bitRead(byte(modedac), 1))); // since fw-v250 we have introduced dac0.50.100 mode so we need to read one more bit in order to configure 3 modes 1890 | } else { 1891 | enabledac = false; // we set bit7 of pwmstate LOW when not using dac output, because it is unused (for simplicity we keep it 0 since this is MSB of pwmstate) 1892 | pwmstate = bitWrite(pwmstate, 6, boolean(bitRead(byte(modepwm), 1))); // only look at bit1 of modepwm (beacause we only have 2 modes: fast pwm and phase correct) 1893 | } 1894 | pwmstate = bitWrite(pwmstate, 7, enabledac); // update DAC enable/disable state (for pwm output mode, it's just 0) 1895 | } 1896 | 1897 | // function that will be called when controller 'numbers' changes 1898 | public void CPR(int n) { 1899 | int m = maxAllowedCPR(wParmFFBprev[0]); // last degrees of rotation 1900 | if (n != lastCPR) { 1901 | //println("received "+ n +" from Numberbox numbers "); 1902 | if (n >= m) { 1903 | n = m; 1904 | CPRlimit = true; 1905 | } 1906 | wb = "O " + str(n); // set command for encoder CPR adjustment 1907 | executeWR(); 1908 | lastCPR = n; 1909 | } 1910 | } 1911 | 1912 | void makeEditable(Numberbox n) { 1913 | // allows the user to click a numberbox and type in a number which is confirmed with RETURN 1914 | final NumberboxInput nin = new NumberboxInput(n); // custom input handler for the numberbox 1915 | // control the active-status of the input handler when releasing the mouse button inside 1916 | // the numberbox. deactivate input handler when mouse leaves. 1917 | n.onClick(new CallbackListener() { 1918 | public void controlEvent(CallbackEvent theEvent) { 1919 | nin.setActive(true); 1920 | } 1921 | } 1922 | ).onLeave(new CallbackListener() { 1923 | public void controlEvent(CallbackEvent theEvent) { 1924 | nin.setActive(false); 1925 | nin.submit(); 1926 | } 1927 | } 1928 | ); 1929 | } 1930 | 1931 | void frequency(int n) { 1932 | /* request the selected item based on index n */ 1933 | //println(n, cp5.get(ScrollableList.class, "frequency").getItem(n)); 1934 | /* here an item is stored as a Map with the following key-value pairs: 1935 | * name, the given name of the item 1936 | * text, the given text of the item by default the same as name 1937 | * value, the given value of the item, can be changed by using .getItem(n).put("value", "abc"); a value here is of type Object therefore can be anything 1938 | * color, the given color of the item, how to change, see below 1939 | * view, a customizable view, is of type CDrawable 1940 | */ 1941 | CColor c = new CColor(); 1942 | c.setBackground(color(255, 0, 0)); 1943 | cp5.get(ScrollableList.class, "frequency").getItem(n).put("color", c); 1944 | freqpwm = n; 1945 | updatePWMstate (); 1946 | if (n < allowedRCMfreqID) { // everything above 500Hz, or lower than freq ID 4 1947 | if (RCMselected) showMessageDialog(frame, "This frequency is not available for RCM mode,\nplease select one of the other available ones.", "Caution", WARNING_MESSAGE); 1948 | } 1949 | } 1950 | 1951 | void pwmtype(int n) { 1952 | CColor c = new CColor(); 1953 | c.setBackground(color(255, 0, 0)); 1954 | cp5.get(ScrollableList.class, "pwmtype").getItem(n).put("color", c); 1955 | typepwm = boolean(n); 1956 | updatePWMstate(); 1957 | } 1958 | 1959 | void pwmmode(int n) { 1960 | CColor c = new CColor(); 1961 | c.setBackground(color(255, 0, 0)); 1962 | cp5.get(ScrollableList.class, "pwmmode").getItem(n).put("color", c); 1963 | modepwm = n; 1964 | updatePWMstate (); 1965 | if (n==3) { // if we selected RCM pwm mode, only available if firmware supports RCM pwm mode 1966 | if (!RCMselected) { // if RCM mode is not selected 1967 | cp5.get(ScrollableList.class, "frequency").removeItems(a1); // remove extended pwm freq selection 1968 | cp5.get(ScrollableList.class, "frequency").addItems(a2_rcm); // add pwm freq selection for RCM pwm mode 1969 | RCMselected = true; 1970 | } 1971 | } else { // if we selected anything else except for RCM mode 1972 | if (RCMselected) { // if previous selection was RCM mode 1973 | cp5.get(ScrollableList.class, "frequency").removeItems(a2_rcm); // remove freq selection for RCM pwm mode 1974 | cp5.get(ScrollableList.class, "frequency").addItems(a1); // add the extented pwm freq selection 1975 | } 1976 | RCMselected = false; 1977 | } 1978 | cp5.get(ScrollableList.class, "frequency").setValue(freqpwm); // update the frequency list to the last freq selection 1979 | } 1980 | 1981 | void dacmode(int n) { 1982 | CColor c = new CColor(); 1983 | c.setBackground(color(255, 0, 0)); 1984 | cp5.get(ScrollableList.class, "dacmode").getItem(n).put("color", c); 1985 | modedac = n; 1986 | updatePWMstate(); 1987 | } 1988 | 1989 | void dacout(int n) { 1990 | CColor c = new CColor(); 1991 | c.setBackground(color(255, 0, 0)); 1992 | cp5.get(ScrollableList.class, "dacmode").getItem(n).put("color", c); 1993 | enabledac = boolean(n); 1994 | updatePWMstate(); 1995 | } 1996 | 1997 | void xFFBaxis(int n) { 1998 | CColor c = new CColor(); 1999 | c.setBackground(color(255, 0, 0)); 2000 | cp5.get(ScrollableList.class, "xFFBaxis").getItem(n).put("color", c); 2001 | xFFBAxisIndex = byte(n); 2002 | updateEffstate(); 2003 | } 2004 | 2005 | void profile(int n) { 2006 | CColor c = new CColor(); 2007 | c.setBackground(color(255, 0, 0)); 2008 | cp5.get(ScrollableList.class, "profile").getItem(n).put("color", c); 2009 | profileActuated = true; 2010 | cur_profile = n; 2011 | if (!profiles[n].isEmpty()) { 2012 | profiles[n].download(); 2013 | //profiles[n].show(); 2014 | setFromProfile(); // update settings from Profile and send to Arduino if non-empty 2015 | } else { 2016 | println(profiles[n].name, "empty"); 2017 | } 2018 | //listProfiles(); 2019 | } 2020 | 2021 | int COMselector() { 2022 | //https://docs.oracle.com/javase/tutorial/uiswing/components/dialog.html 2023 | //show dialog window to select a serial device 2024 | String COMx, COMlist = ""; 2025 | int result; 2026 | try { 2027 | if (debug) printArray(Serial.list()); 2028 | int i = Serial.list().length; 2029 | if (i != 0) { 2030 | if (i >= 2) { 2031 | // need to check which port the inst uses - 2032 | // for now we'll just let the user decide 2033 | for (int j = 0; j < i; ) { 2034 | COMlist += "(" +char(j+'a') + ") " + Serial.list()[j]; 2035 | if (++j < i) COMlist += ", "; 2036 | } 2037 | COMx = showInputDialog(frame, "Step 2 of setup passed succesfuly, we're almost finished.\n\t" + gpad + " at? (type letter only)\n" + COMlist, "Setup - step 3/3", QUESTION_MESSAGE); 2038 | if (COMx == null) exit(); 2039 | if (COMx.isEmpty()) exit(); 2040 | 2041 | i = int(COMx.toLowerCase().charAt(0) - 'a') + 1; 2042 | } 2043 | String portName = Serial.list()[i-1]; 2044 | if (debug) println(gpad, "at", portName); 2045 | myPort = new Serial(this, portName, 115200); // change baud rate to your liking 2046 | //myPort.bufferUntil('\n'); // buffer until CR/LF appears, but not required.. 2047 | result = i-1; 2048 | //exit(); 2049 | } else { 2050 | showMessageDialog(frame, "No serial port deviced detected.\n", "Warning", WARNING_MESSAGE); 2051 | result = 0; 2052 | //exit(); 2053 | } 2054 | } 2055 | catch (Exception e) 2056 | { //Print the type of error 2057 | showMessageDialog(frame, "Selected COM port is not available,\ndoes not exist or may be in use by\nanother program.\n", "Setup Error", ERROR_MESSAGE); 2058 | println("Error:", e); 2059 | result = 0; 2060 | //exit(); 2061 | } 2062 | return result; 2063 | } 2064 | 2065 | public void handleSliderEvents(GValueControl slider, GEvent event) { 2066 | int sdrID=0; 2067 | for (int i=0; i= maxAllowedDeg(lastCPR)) { 2083 | slider_value[0] = maxAllowedDeg(lastCPR); 2084 | slider.setValue(maxAllowedDeg(lastCPR)/deg_max); 2085 | } 2086 | wParmFFB [0] = slider_value[0]; 2087 | } else if (sdrID == 10) { 2088 | slider_value[10] = slider.getValueF()*minPWM_max; 2089 | if (slider_value[10] > minPWM_max) { 2090 | slider_value[10] = minPWM_max; 2091 | slider.setValue(1.0); 2092 | } 2093 | wParmFFB [10] = slider_value[10]; 2094 | } else if (sdrID == 11) { 2095 | slider_value[11] = round(slider.getValueF()*brake_max); 2096 | if (slider_value[11] < brake_min) { 2097 | slider_value[11] = brake_min; 2098 | slider.setValue(0.0); 2099 | } 2100 | if (slider_value[11] > brake_max) { 2101 | slider_value[11] = brake_max; 2102 | slider.setValue(1.0); 2103 | } 2104 | wParmFFB [11] = slider_value[11]; 2105 | } 2106 | } 2107 | 2108 | String[] ProfileNameList() { 2109 | String[] temp = new String[num_profiles]; 2110 | for (int i=0; i= maxCPR) { 2141 | temp = maxCPR; 2142 | } 2143 | return temp; 2144 | } 2145 | 2146 | float maxAllowedDeg (float cpr) { // maximum allowed rotation degrees range for any given encoder CPR 2147 | float temp = 0.0; 2148 | temp = float(maxCPR_turns)/(cpr/360.0); 2149 | if (temp >= deg_max) { 2150 | temp = deg_max; 2151 | } 2152 | return temp; 2153 | } 2154 | 2155 | void SetAxisColors() { // set default, load or save axis colors into a txt file 2156 | File ac = new File(dataPath("axisColor_cfg.txt")); 2157 | if (!ac.exists()) { // if file does not exist 2158 | for (int i=0; i0; i--) { 2189 | setupTextBuffer[i] = setupTextBuffer[i-1]; 2190 | } 2191 | } 2192 | 2193 | // clear setup text buffer 2194 | void clearSetupTextBuffer() { 2195 | for (int i=0; i= maxWidth) maxWidth = textWidth(setupTextBuffer[i]); 2205 | } 2206 | for (int i=1; i<=setupTextLength; i++) { 2207 | //if (setupTextBuffer[i].equals(" ")) break; // do not show empty lines 2208 | fill(51, a); 2209 | strokeWeight(1); 2210 | stroke(51, a); 2211 | rect(20, height-(i+1)*font_size, maxWidth, -1.1*font_size); 2212 | pushMatrix(); 2213 | translate(20, height-(i+1.1)*font_size); 2214 | fill(255, a); 2215 | textSize(font_size); 2216 | text(setupTextBuffer[i], 0, 0); 2217 | popMatrix(); 2218 | } 2219 | } 2220 | 2221 | void animateSetupTextBuffer(int frames) { 2222 | float t = (float(frames) / frameRate); // update timer for showing setup text 2223 | float as = (1000.0 * setupTextInitAlpha) / (float(setupTextFadeout_ms) * frameRate); // fade out step for setupTextAlpha in each frame 2224 | float t_s = float(setupTextTimeout_ms)/1000.0; // timeout in s 2225 | if (t > t_s) { 2226 | setupTextAlpha -= as; // decrease setupTextAlpha until 0 2227 | if (setupTextAlpha < 0) { 2228 | setupTextAlpha = 0; // prevent negative 2229 | showSetupText = false; 2230 | } 2231 | } 2232 | drawSetupTextBuffer(setupTextAlpha); 2233 | } 2234 | 2235 | void ExportSetupTextLog() { 2236 | String[] e = new String[setupTextLength]; 2237 | for (int i=0; i= x-sx/2 && mouseX <= x+sx/2 && mouseY >= y-sy/2 && mouseY <= y+sy/2) { // if mouse pointer is howered over 23 | controlb[i] = true; 24 | } else { 25 | controlb[i] = false; 26 | } 27 | if (controlb[i] && mousePressed) { // green with black text (activated) 28 | col[0] = 96; 29 | col[1] = 200; 30 | col[2] = 150; 31 | thue = 0; 32 | changing = true; 33 | } else if (controlb[i] && !mousePressed) { // yellow with white text (howered) 34 | col[0] = 40; 35 | col[1] = 200; 36 | col[2] = 180; 37 | thue = 255; 38 | changing = false; 39 | } else if (!controlb[i] || !grabed) { // red with white text (deactivated) 40 | col[0] = 0; 41 | col[1] = 200; 42 | col[2] = 150; 43 | thue = 255; 44 | changing = false; 45 | } 46 | } else { 47 | col[0] = 0; 48 | col[1] = 0; 49 | col[2] = 100; 50 | thue = 255; 51 | } 52 | } 53 | 54 | void showPointer(float dx, float dy) { 55 | float yoffs = sy+2; // pointer y offset 56 | pushMatrix(); 57 | fill(col[0], col[1], col[2]); 58 | strokeWeight(1); 59 | stroke(255); 60 | if (orn == 0) { 61 | translate(x, y); 62 | } else if (orn == 1) { 63 | translate(x, y); 64 | } else if (orn == 2) { 65 | translate(x, y); 66 | } else if (orn == 3) { 67 | translate(x, y); 68 | } 69 | rotate(float(orn)/2.0*PI); 70 | beginShape(); 71 | vertex(-sx/2, -sy/2); 72 | vertex(sx/2, -sy/2); 73 | vertex(sx/2, sy/2); 74 | vertex(0, sy); 75 | vertex(-sx/2, sy/2); 76 | vertex(-sx/2, -sy/2); 77 | endShape(); 78 | textSize(font_size); 79 | if (sx < font_size) textSize(sx); 80 | fill(thue); 81 | stroke(150); 82 | if (orn == 0) { 83 | text(t, -sx/2, sy/2); 84 | pushMatrix(); 85 | translate(0, yoffs); 86 | line(0, 1, 0, dy-1); 87 | popMatrix(); 88 | } else if (orn == 1) { 89 | pushMatrix(); 90 | rotate(-float(orn)/2.0*PI); 91 | translate(-yoffs, 0); 92 | text(t, sx, sy/2); 93 | line(-dx+1, 0, -1, 0); 94 | popMatrix(); 95 | } else if (orn == 2) { 96 | pushMatrix(); 97 | translate(0, yoffs); 98 | line(0, 1, 0, dy-1); 99 | rotate(-float(orn)/2.0*PI); 100 | text(t, -sx/2, 1.5*sy); 101 | popMatrix(); 102 | } else if (orn == 3) { 103 | pushMatrix(); 104 | rotate(-float(orn)/2.0*PI); 105 | text(t, -sx/2, 0.35*sy); 106 | translate(dx+yoffs, 0); 107 | line(-dx+1, 0, -1, 0); 108 | popMatrix(); 109 | } 110 | popMatrix(); 111 | textSize(font_size); 112 | } 113 | } 114 | --------------------------------------------------------------------------------