-
Notifications
You must be signed in to change notification settings - Fork 75
/
calc.c
458 lines (371 loc) · 12.3 KB
/
calc.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
/*
calc - calculate values of ppm signal
Copyright (C) 2011 Pavel Semerad
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "calc.h"
#include "menu.h"
#include "ppm.h"
#include "config.h"
#include "input.h"
#include "timer.h"
#define ABS_THRESHOLD PPM(50)
@near static s16 last_value[MAX_CHANNELS];
// CALC task
TASK(CALC, 90);
static void calc_loop(void);
// initialize CALC task
void calc_init(void) {
build(CALC);
activate(CALC, calc_loop);
sleep(CALC); // no work yet, waked up after setting number of channels
}
// limit adc value to -5000..5000 (standard servo signal * 10)
static s16 channel_calib(u16 adc_ovs, u16 call, u16 calm, u16 calr, u16 dead) {
s16 val;
if (adc_ovs < calm) {
// left part
if (adc_ovs < call) adc_ovs = call; // limit to calib left
val = (s16)adc_ovs - (s16)(calm - dead);
if (val >= 0) return 0; // in dead zone
return (s16)((s32)val * PPM(500) / ((calm - dead) - call));
}
else {
// right part
if (adc_ovs > calr) adc_ovs = calr; // limit to calib right
val = (s16)adc_ovs - (s16)(calm + dead);
if (val <= 0) return 0; // in dead zone
return (s16)((s32)val * PPM(500) / (calr - (calm + dead)));
}
}
// apply reverse, endpoint, subtrim, trim (for channel 1-2)
// set value to ppm channel
static void channel_params(u8 channel, s16 inval) {
s8 trim = 0;
s16 val;
s32 trim32 = 0;
// if value forced from menu (settting endpoints, subtrims, ...), set it
if (menu_force_value_channel == channel)
inval = menu_force_value;
// check limits -5000..5000
if (inval < PPM(-500)) inval = PPM(-500);
else if (inval > PPM(500)) inval = PPM(500);
// save last value
last_value[channel - 1] = inval;
// read trims for channels 1-2 and compute inval offset
if (channel == 1 && cm.channel_DIG != 1)
// steering and not dual-ESC steering
trim = cm.trim_steering;
else if (channel == 2 || channel == cm.channel_DIG)
// throttle and also second throttle from channel_DIG
trim = cm.trim_throttle;
if (trim && inval) // abs(inval) * (trim * 10) / 5000 -> 100x more
trim32 = ((s32)(inval < 0 ? -inval : inval) * trim + 2) / 5;
// apply endpoint and trim32
val = (s16)(((s32)inval * cm.endpoint[channel-1][(u8)(inval < 0 ? 0 : 1)] -
trim32) / 100);
// add subtrim, trim and reverse
val += (cm.subtrim[channel-1] + trim) * PPM(1);
if (cm.reverse & (u8)(1 << (channel - 1))) val = -val;
// set value for this ppm channel
ppm_set_value(channel, (u16)(PPM(1500) + val));
}
// expo only for plus values: x: 0..5000, exp: 1..99
static s16 expou(u16 x, u8 exp) {
// (x * x * x * exp / (5000 * 5000) + x * (100 - exp) + 50) / 100
return (s16)(((u32)x * x / PPM(500) * x * exp / PPM(500)
+ (u32)x * (u8)(100 - exp) + 50) / 100);
}
// apply expo: inval: -5000..5000, exp: -99..99
static s16 expo(s16 inval, s8 exp) {
u8 neg;
s16 val;
if (exp == 0) return inval; // no expo
if (inval == 0) return inval; // 0 don't change
neg = (u8)(inval < 0 ? 1 : 0);
if (neg) inval = -inval;
if (exp > 0) val = expou(inval, exp);
else val = PPM(500) - expou(PPM(500) - inval, (u8)-exp);
return neg ? -val : val;
}
// apply dualrate
@inline static s16 dualrate(s16 val, u8 dr) {
return (s16)((s32)val * dr / 100);
}
// apply steering speed
static s16 steering_speed(s16 val, u8 channel) {
s16 last = last_value[channel - 1];
s16 delta = val - last;
s16 delta2 = 0;
s16 max_delta;
u8 stspd;
if (!delta) return val; // no change from previous val
if (cm.stspd_turn == 100 && cm.stspd_return == 100) return val; // max spd
if (!last) stspd = cm.stspd_turn; // it is always turn from centre
else if (last < 0) {
// last was left
if (val < last) stspd = cm.stspd_turn; // more left turn
else {
// right from previous
if (val <= 0) stspd = cm.stspd_return; // return max to centre
else {
// right to right side of centre
stspd = cm.stspd_return;
delta = -last;
delta2 = val;
}
}
}
else {
// last was right
if (val > last) stspd = cm.stspd_turn; // more right turn
else {
// left from previous
if (val >= 0) stspd = cm.stspd_return; // return max to centre
else {
// left to left side of centre
stspd = cm.stspd_return;
delta = -last;
delta2 = val;
}
}
}
// calculate max delta
if (stspd == 100) max_delta = PPM(1000);
else max_delta = PPM(1000) / 2 / 20 * ppm_frame_length / (100 - stspd);
// compare delta with max_delta
if (delta < 0) {
if (max_delta < -delta) {
// over
val = last - max_delta;
delta2 = 0; // nothing at turn side
}
}
else {
if (max_delta < delta) {
// over
val = last + max_delta;
delta2 = 0; // nothing at turn side
}
}
// check if it is moving from return to turn
if (delta2) {
if (cm.stspd_turn == 100) max_delta = PPM(1000);
else max_delta = PPM(1000) / 2 / 20 * ppm_frame_length / (100 - cm.stspd_turn);
if (delta2 < 0) {
if (max_delta < -delta2) val = -max_delta;
}
else {
if (max_delta < delta2) val = max_delta;
}
}
return val;
}
// apply servo speed
static s16 channel_speed(s16 val, u8 channel) {
s16 last = last_value[channel - 1];
s16 delta = val - last;
s16 max_delta;
u8 speed = cm.speed[channel - 1];
// for DIG channel use throttle speed
if (channel == cm.channel_DIG) speed = cm.thspd;
if (!delta) return val; // no change from previous val
if (speed == 100) return val; // max speed
// special handling for "throttle only for forward"
if (cm.thspd_onlyfwd && (channel == 2 || channel == cm.channel_DIG)) {
if (val >= 0 && !cm.brake_off) return val; // at brake side
if (delta > 0) return val; // not forwarding
if (last > 0 && !cm.brake_off) {
// apply speed only from centre to forward
last = 0;
delta = val;
}
}
max_delta = PPM(1000) / 2 / 20 * ppm_frame_length / (100 - speed);
if (delta < 0) {
if (max_delta < -delta) val = last - max_delta;
}
else {
if (max_delta < delta) val = last + max_delta;
}
return val;
}
// calculate new PPM values from ADC and internal variables
// called for each PPM cycle
static void calc_loop(void) {
s16 val, val2;
u8 i, bit;
s16 DIG_mix;
u16 adc_steering, adc_throttle; // last 4 or 1 ADC values
while (1) {
// handle channel3 potentiometer, cannot use channel_calib,
// because we don't have calib middle and dead zone
if (cg.ch3_pot && !menu_ch3_pot_disabled) {
// do it only if some function is assigned to CH3 pot
if (*ck_ch3_pot_func) {
if (cg.adc_ovs_last) val = adc_ch3_last << ADC_OVS_SHIFT;
else val = adc_ch3_ovs;
// map ch3 pot -5000..5000 range
val -= cg.calib_ch3_left << ADC_OVS_SHIFT;
val2 = (s16)((s32)(val) * PPM(1000) /
((cg.calib_ch3_right - cg.calib_ch3_left) << ADC_OVS_SHIFT))
- PPM(500);
// safety checks to -5000..5000 limits
if (val2 < PPM(-500)) val2 = PPM(-500);
else if (val2 > PPM(500)) val2 = PPM(500);
// do reverse
if (*ck_ch3_pot_rev) val2 = -val2;
// set value to function
menu_et_function_set_from_linear(*ck_ch3_pot_func, val2);
}
}
DIG_mix = menu_DIG_mix * PPM(5); // to -5000..5000 range
// set used ADC values
if (cg.adc_ovs_last) {
// last value
adc_steering = adc_steering_last << ADC_OVS_SHIFT;
adc_throttle = adc_throttle_last << ADC_OVS_SHIFT;
}
else {
// oversampled last 4 values
adc_steering = adc_steering_ovs;
adc_throttle = adc_throttle_ovs;
}
// steering
val = channel_calib(adc_steering,
cg.calib_steering_left << ADC_OVS_SHIFT,
cg.calib_steering_mid << ADC_OVS_SHIFT,
cg.calib_steering_right << ADC_OVS_SHIFT,
cg.steering_dead_zone << ADC_OVS_SHIFT);
val = expo(val, cm.expo_steering);
val = dualrate(val, cm.dr_steering);
if (cm.channel_DIG != 1) {
// channel 1 is normal servo steering
if (!cm.channel_4WS)
channel_params(1, steering_speed(val, 1));
else {
// 4WS mixing
val2 = val;
if (menu_4WS_crab) val2 = -val2; // apply crab
if (menu_4WS_mix < 0)
// reduce front steering
val = (s16)((s32)val * (100 + menu_4WS_mix) / 100);
else if (menu_4WS_mix > 0)
// reduce rear steering
val2 = (s16)((s32)val2 * (100 - menu_4WS_mix) / 100);
channel_params(1, steering_speed(val, 1));
channel_params(cm.channel_4WS,
steering_speed(val2, cm.channel_4WS));
}
}
else {
// channel 1 is part of dual-ESC steering
@near static s16 last_ch1;
// apply steering trim to val
if (cm.trim_steering) {
val2 = (s16)(((s32)(val < 0 ? -val : val) *
cm.trim_steering + 250) / PPM(500 / 10));
val = val - val2 + cm.trim_steering * PPM(1);
}
// return back value from steering wheel to allow to use
// steering speed
val2 = last_value[0];
last_value[0] = last_ch1;
val = steering_speed(val, 1);
if (val < PPM(-500)) val = PPM(-500);
else if (val > PPM(500)) val = PPM(500);
// save steering value for steering speed
last_ch1 = val;
last_value[0] = val2;
// set DIG mix
DIG_mix = -val; // minus, because 100 will reduce channel 1
menu_DIG_mix = (s8)(DIG_mix / PPM(5));
// set it 2 times more to have contra ESC steering, it can be
// reduced by D/R setting
if (!cm.brake_off) DIG_mix *= 2;
}
// throttle
if (menu_brake)
val = PPM(500); // brake button overrides throttle
else
val = channel_calib(adc_throttle,
cg.calib_throttle_fwd << ADC_OVS_SHIFT,
cg.calib_throttle_mid << ADC_OVS_SHIFT,
cg.calib_throttle_bck << ADC_OVS_SHIFT,
cg.throttle_dead_zone << ADC_OVS_SHIFT);
val = expo(val, (u8)(val < 0 ? cm.expo_forward : cm.expo_back));
if (cm.abs_type) {
// apply selected ABS
static u8 abs_time;
static _Bool abs_state; // when 1, lower brake value
// cycle time in 1ms steps: 120/80/60ms
u8 cycle_time = (u8)(cm.abs_type == 1 ? 120 :
cm.abs_type == 2 ? 80 : 60);
if (val > ABS_THRESHOLD) {
// check time with 40ms reserve and change abs_state
if ((u8)(ppm_timer - abs_time) <= 40) {
abs_time = (u8)(ppm_timer + cycle_time);
abs_state ^= 1;
}
// apply ABS
if (abs_state)
val /= 2;
}
else {
// no ABS
abs_time = (u8)(ppm_timer + cycle_time);
abs_state = 0;
}
}
val = dualrate(val, (u8)(val < 0 ? cm.dr_forward : cm.dr_back));
// brake to extra channel
if (cm.channel_brake) {
val2 = val;
if (val2 < 0) val2 = 0; // eliminate forward
val2 = val2 * 2 - PPM(500); // to whole servo range
channel_params(cm.channel_brake, channel_speed(val2, cm.channel_brake));
}
// throttle brake cut off
if (cm.brake_off && val > 0) val = 0;
// set to servo
if (!cm.channel_DIG) {
if (cm.brake_off) val = val * 2 + PPM(500);
channel_params(2, channel_speed(val, 2));
}
else {
// DIG mixing
val2 = val;
if (menu_DIG_mix < 0)
// reduce front throttle
val = (s16)((s32)val * (PPM(500) + DIG_mix) / PPM(500));
else if (menu_DIG_mix > 0)
// reduce rear throttle
val2 = (s16)((s32)val2 * (PPM(500) - DIG_mix) / PPM(500));
if (cm.brake_off) {
val = val * 2 + PPM(500);
val2 = val2 * 2 + PPM(500);
}
channel_params(2, channel_speed(val, 2));
channel_params(cm.channel_DIG, channel_speed(val2, cm.channel_DIG));
}
// channels 3-8, exclude mixed channels in the future
for (i = 3, bit = 0b100; i <= channels; i++, bit <<= 1) {
// check if channel was already mixed before (4WS, DIG)
if (menu_channels_mixed & bit) continue;
channel_params(i, channel_speed(menu_channel3_8[i - 3] * PPM(5), i));
}
// sync signal
ppm_calc_sync();
// wait for next cycle
stop();
}
}