├── 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 |
--------------------------------------------------------------------------------