Open FFBoard
Open source force feedback firmware
HidFFB.cpp
Go to the documentation of this file.
1/*
2 * HidFFB.cpp
3 *
4 * Created on: 12.02.2020
5 * Author: Yannick
6 */
7
8#include <assert.h>
9#include "HidFFB.h"
10#include "flash_helpers.h"
11#include "hid_device.h"
12#include "cppmain.h"
13#include <math.h>
14
15HidFFB::HidFFB(std::shared_ptr<EffectsCalculator> ec,uint8_t axisCount) : effects_calc(ec), effects(ec->effects),axisCount(axisCount)
16{
17 directionEnableMask = 1 << axisCount; // Direction enable bit is last bit after axis enable bits
18 // Initialize reports
19 blockLoad_report.effectBlockIndex = 1;
20 blockLoad_report.ramPoolAvailable = (effects.size()-used_effects)*sizeof(FFB_Effect);
21 blockLoad_report.loadStatus = 1;
22
23 pool_report.ramPoolSize = effects.size()*sizeof(FFB_Effect);
24 pool_report.maxSimultaneousEffects = effects.size();
25 pool_report.memoryManagement = 1;
26
27
28 this->registerHidCallback();
29}
30
32}
33
34
39 this->directionEnableMask = mask;
40}
41
42
44 return this->ffb_active;
45}
46
47bool HidFFB::HID_SendReport(uint8_t *report,uint16_t len){
48 return tud_hid_report(0, report, len); // ID 0 skips ID field
49}
50
51
55void HidFFB::sendStatusReport(uint8_t effect){
56// if(effect != 0){
57// this->reportFFBStatus.effectBlockIndex = effect;
58// }
59 this->reportFFBStatus.status = HID_ACTUATOR_POWER;
60 if(this->ffb_active){
61 this->reportFFBStatus.status |= HID_ENABLE_ACTUATORS;
62 this->reportFFBStatus.status |= HID_EFFECT_PLAYING;
63 }else{
64 this->reportFFBStatus.status |= HID_EFFECT_PAUSE;
65 }
66// if(effect > 0 && effects[effect-1].state == 1)
67// this->reportFFBStatus.status |= HID_EFFECT_PLAYING;
68 //printf("Status: %d\n",reportFFBStatus.status);
69 HID_SendReport(reinterpret_cast<uint8_t*>(&this->reportFFBStatus), sizeof(reportFFB_status_t));
70}
71
72
76void HidFFB::hidOut(uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize){
77 fxUpdateEvent(); // use uint16_t for timer overflow handling if micros timer is used
78
79 // FFB Output Message
80 const uint8_t* report = buffer;
81 uint8_t event_idx = report_id - FFB_ID_OFFSET;
82
83 // -------- Out Reports --------
84 switch(event_idx)
85 {
86
87 case HID_ID_NEWEFREP: //add Effect Report. Feature
88 new_effect((FFB_CreateNewEffect_Feature_Data_t*)(report));
89 break;
90 case HID_ID_EFFREP: // Set Effect
91 {
92 FFB_SetEffect_t setEffectRepBuf;
93 memcpy(&setEffectRepBuf,report,std::min<uint16_t>(sizeof(FFB_SetEffect_t),bufsize)); // Copy report to buffer. only valid range if less axes are used
94 set_effect(&setEffectRepBuf);
95 break;
96 }
97 case HID_ID_CTRLREP: // Control report. 1=Enable Actuators, 2=Disable Actuators, 4=Stop All Effects, 8=Reset, 16=Pause, 32=Continue
98 ffb_control(report[1]);
99 //sendStatusReport(0);
100 break;
101 case HID_ID_GAINREP: // Set global gain
102 set_gain(report[1]);
103 break;
104 case HID_ID_ENVREP: // Envelope
105 set_envelope((FFB_SetEnvelope_Data_t *)report);
106 break;
107 case HID_ID_CONDREP: // Spring, Damper, Friction, Inertia
108 set_condition((FFB_SetCondition_Data_t*)report);
109 break;
110 case HID_ID_PRIDREP: // Periodic
111 set_periodic((FFB_SetPeriodic_Data_t*)report);
112 break;
113 case HID_ID_CONSTREP: // Constant
114 set_constant_effect((FFB_SetConstantForce_Data_t*)report);
115 break;
116 case HID_ID_RAMPREP: // Ramp
117 set_ramp((FFB_SetRamp_Data_t *)report);
118 break;
119 case HID_ID_CSTMREP: // Custom. pretty much never used
120 //printf("Customrep");
121 break;
122 case HID_ID_SMPLREP: // Download sample
123 //printf("Sampledl");
124 break;
125 case HID_ID_EFOPREP: //Effect operation
126 {
127 set_effect_operation((FFB_EffOp_Data_t*)report);
128 break;
129 }
130 case HID_ID_BLKFRREP: // Free a block
131 {
132 effects_calc->free_effect(report[1]-1);
133 break;
134 }
135
136 default:
137 {
138 break;
139 }
140 }
141
142}
143
144
151uint16_t HidFFB::hidGet(uint8_t report_id, hid_report_type_t report_type,uint8_t* buffer, uint16_t reqlen){
152 // Feature gets go here
153
154 uint8_t id = report_id - FFB_ID_OFFSET;
155
156 switch(id){
157 case HID_ID_BLKLDREP:
158 //printf("Get Block Report\n");
159 // Notice: first byte ID is not present in the reply buffer because it is handled by tinyusb internally!
160 memcpy(buffer,&this->blockLoad_report,sizeof(FFB_BlockLoad_Feature_Data_t));
161 return sizeof(FFB_BlockLoad_Feature_Data_t);
162 break;
163 case HID_ID_POOLREP:
164 //printf("Get Pool Report\n");
165 memcpy(buffer,&this->pool_report,sizeof(FFB_PIDPool_Feature_Data_t));
166 return sizeof(FFB_PIDPool_Feature_Data_t);
167 break;
168 default:
169 break;
170 }
171 return 0;
172}
173
175#ifdef DEBUGLOG
177#endif
178 this->set_FFB(true);
179}
180
182#ifdef DEBUGLOG
184#endif
185 this->set_FFB(false);
186}
187
188void HidFFB::set_FFB(bool state)
189{
190 assert(effects_calc != nullptr);
191 this->ffb_active = state;
192 effects_calc->setActive(state);
193}
194
195void HidFFB::set_gain(uint8_t gain){
196 assert(effects_calc != nullptr);
197 effects_calc->setGain(gain);
198}
199
201 assert(effects_calc != nullptr);
202 effects_calc->setFilters(effect);
203}
204
205void HidFFB::ffb_control(uint8_t cmd){
206
207 if(cmd & 0x01){ //enable
208 start_FFB();
209 }if(cmd & 0x02){ //disable
210 stop_FFB();
211 }if(cmd & 0x04){ //stop
212 stop_FFB();
213 //start_FFB();
214 }if(cmd & 0x08){ //reset
215 //ffb_active = true;
216 stop_FFB();
217 reset_ffb();
218 // reset effects
219 }if(cmd & 0x10){ //pause
220 stop_FFB();
221 }if(cmd & 0x20){ //continue
222 start_FFB();
223 }
224}
225
226
227void HidFFB::set_constant_effect(FFB_SetConstantForce_Data_t* data){
228 if(data->effectBlockIndex == 0 || data->effectBlockIndex > effects.size()){
229 return;
230 }
232 FFB_Effect& effect_p = effects[data->effectBlockIndex-1];
233
234 effect_p.magnitude = data->magnitude;
235// if(effect_p.state == 0){
236// effect_p.state = 1; // Force start effect
237// }
238}
239
240void HidFFB::new_effect(FFB_CreateNewEffect_Feature_Data_t* effect){
241 // Allocates a new effect
242
243 int32_t index = effects_calc->find_free_effect(effect->effectType); // next effect
244 if(index == -1){
245 blockLoad_report.loadStatus = 2;
246#ifdef DEBUGLOG
247 CommandHandler::logSerialDebug("Can't allocate a new effect");
248#endif
249 return;
250 }
252 new_effect.type = effect->effectType;
253
254 this->effects_calc->logEffectType(effect->effectType,false);
255#ifdef DEBUGLOG
256 CommandHandler::logSerialDebug("New effect type:" + std::to_string(effect->effectType) + " idx: " + std::to_string(index-1));
257#endif
258
260
261 effects[index] = std::move(new_effect);
262 // Set block load report
263 //reportFFBStatus.effectBlockIndex = index;
264 blockLoad_report.effectBlockIndex = index+1;
265 used_effects++;
266 blockLoad_report.ramPoolAvailable = (effects.size()-used_effects)*sizeof(FFB_Effect);
267 blockLoad_report.loadStatus = 1;
268 sendStatusReport(index+1);
269
270
271}
272
277void HidFFB::set_effect(FFB_SetEffect_t* effect){
278 uint8_t index = effect->effectBlockIndex;
279 if(index > effects.size() || index == 0)
280 return;
281
282 FFB_Effect* effect_p = &effects[index-1];
283
284 if (effect_p->type != effect->effectType){
285 effect_p->startTime = 0;
286 set_filters(effect_p);
287 }
288
289 effect_p->gain = effect->gain;
290 effect_p->type = effect->effectType;
291 effect_p->samplePeriod = effect->samplePeriod;
292
293 bool directionEnable = (effect->enableAxis & this->directionEnableMask);
294 bool overridesCondition = false;
295
296// if(effect_p->useSingleCondition){ // Only allow turning single condition off in case it was overridden by sending multiple conditions previously
297// effect_p->useSingleCondition = directionEnable; // If direction is used only a single parameter block is allowed. Somehow this is still set while 2 conditions are sent...
298// }
299 // Conditional effects usually do not use directions
300 if(!effect_p->useSingleCondition && (effect->effectType == FFB_EFFECT_SPRING || effect->effectType == FFB_EFFECT_DAMPER || effect->effectType == FFB_EFFECT_INERTIA || effect->effectType == FFB_EFFECT_FRICTION))
301 {
302 if(effect_p->conditions[0].isActive()){
303 effect_p->axisMagnitudes[0] = 1.0f;
304 overridesCondition = true;
305 }
306 if(effect_p->conditions[1].isActive() || effect_p->useSingleCondition){
307 effect_p->axisMagnitudes[1] = 1.0f;
308 overridesCondition = true;
309 }
310 }
311
312
313 if(!overridesCondition){
314 float phaseX = M_PI*2.0 * (effect->directionX/36000.0f);
315
316 effect_p->axisMagnitudes[0] = directionEnable ? sin(phaseX) : (effect->enableAxis & X_AXIS_ENABLE ? (effect->directionX - 18000.0f) / 18000.0f : 0); // Angular vector if dirEnable used otherwise linear or 0 if axis enabled
317 effect_p->axisMagnitudes[1] = directionEnable ? -cos(phaseX) : (effect->enableAxis & Y_AXIS_ENABLE ? -(effect->directionY - 18000.0f) / 18000.0f : 0);
318 }
319
320#if MAX_AXIS == 3
321 float phaseY = M_PI*2.0 * (effect->directionY/36000.0);
322 effect_p->axisMagnitudes[3] = directionEnable ? sin(phaseY) : (effect->enableAxis & Z_AXIS_ENABLE ? (effect->directionZ - 18000.0f) / 18000.0f : 0);
323#endif
324 if(effect->duration == 0){ // Fix for games assuming 0 is infinite
325 effect_p->duration = FFB_EFFECT_DURATION_INFINITE;
326 }else{
327 effect_p->duration = effect->duration;
328 }
329 effect_p->startDelay = effect->startDelay;
330 if(!ffb_active)
331 start_FFB();
332
333 sendStatusReport(effect->effectBlockIndex);
334 //CommandHandler::logSerialDebug("Setting Effect: " + std::to_string(effect->effectType) + " at " + std::to_string(index) + "\n");
335}
336
350void HidFFB::set_condition(FFB_SetCondition_Data_t *cond){
351 if(cond->effectBlockIndex == 0 || cond->effectBlockIndex > effects.size()){
352 return;
353 }
354 uint8_t axis = std::min(axisCount,cond->parameterBlockOffset);
355
356 FFB_Effect *effect = &effects[cond->effectBlockIndex - 1];
357 effect->conditions[axis].cpOffset = cond->cpOffset;
358 effect->conditions[axis].negativeCoefficient = cond->negativeCoefficient;
359 effect->conditions[axis].positiveCoefficient = cond->positiveCoefficient;
360 effect->conditions[axis].negativeSaturation = cond->negativeSaturation;
361 effect->conditions[axis].positiveSaturation = cond->positiveSaturation;
362 effect->conditions[axis].deadBand = cond->deadBand;
363
364// if(effect->conditions[axis].positiveSaturation == 0){
365// effect->conditions[axis].positiveSaturation = 0x7FFF;
366// }
367// if(effect->conditions[axis].negativeSaturation == 0){
368// effect->conditions[axis].negativeSaturation = 0x7FFF;
369// }
370
371 if(axis>0 && axis < MAX_AXIS && effect->conditions[axis].isActive()){ // Workaround when direction enable is set but multiple conditions are defined... Resets direction and uses conditions again
372 effect->useSingleCondition = false;
373 }
374 if((effect->conditions[axis].isActive() || (axis > 0 && effect->useSingleCondition)) && effect->axisMagnitudes[axis] == 0){
375 effect->axisMagnitudes[axis] = 1.0;
376 }
377
378// for(uint8_t i = 0;i<MAX_AXIS;i++){
379// effect->axisMagnitudes[i] = 1.0;
380// }
381}
382
383void HidFFB::set_effect_operation(FFB_EffOp_Data_t* report){
384 if(report->effectBlockIndex == 0 || report->effectBlockIndex > effects.size()){
385 return; // Invalid ID
386 }
387 // Start or stop effect
388 uint8_t id = report->effectBlockIndex-1;
389 if(report->state == 3){
390 effects[id].state = 0; //Stop
391#ifdef DEBUGLOG
392 CommandHandler::logSerialDebug("Stop effect: " + std::to_string(id));
393#endif
394
395 }else{
396
397 // 1 = start, 2 = start solo
398 if(report->state == 2){
399#ifdef DEBUGLOG
400 CommandHandler::logSerialDebug("Start solo: " + std::to_string(id));
401#endif
402 for(FFB_Effect& effect : effects){
403 effect.state = 0; // Stop all other effects
404 }
405 }
406 if(effects[id].state != 1){
407 set_filters(&effects[id]);
408 }
409#ifdef DEBUGLOG
410 CommandHandler::logSerialDebug("Start effect: " + std::to_string(id));
411#endif
412 effects[id].startTime = HAL_GetTick() + effects[id].startDelay; // + effects[id].startDelay;
413 effects[id].state = 1; //Start
414
415
416 }
417 //sendStatusReport(report[1]);
418 this->effects_calc->logEffectState(effects[id].type,effects[id].state);
419}
420
421
422void HidFFB::set_envelope(FFB_SetEnvelope_Data_t *report){
423 if(report->effectBlockIndex == 0 || report->effectBlockIndex > effects.size()){
424 return;
425 }
426 FFB_Effect *effect = &effects[report->effectBlockIndex - 1];
427
428 effect->attackLevel = report->attackLevel;
429 effect->attackTime = report->attackTime;
430 effect->fadeLevel = report->fadeLevel;
431 effect->fadeTime = report->fadeTime;
432 effect->useEnvelope = true;
433}
434
435void HidFFB::set_ramp(FFB_SetRamp_Data_t *report){
436 if(report->effectBlockIndex == 0 || report->effectBlockIndex > effects.size()){
437 return;
438 }
439 FFB_Effect *effect = &effects[report->effectBlockIndex - 1];
440 effect->magnitude = 0x7fff; // Full magnitude for envelope calculation. This effect does not have a periodic report
441 effect->startLevel = report->startLevel;
442 effect->endLevel = report->endLevel;
443}
444
445void HidFFB::set_periodic(FFB_SetPeriodic_Data_t* report){
446 if(report->effectBlockIndex == 0 || report->effectBlockIndex > effects.size()){
447 return;
448 }
449 FFB_Effect* effect = &effects[report->effectBlockIndex-1];
450
451 effect->period = clip<uint32_t,uint32_t>(report->period,1,0x7fff); // Period is never 0
452 effect->magnitude = report->magnitude;
453 effect->offset = report->offset;
454 effect->phase = report->phase;
455 //effect->counter = 0;
456}
457
458
460 for(uint8_t i=0;i<effects.size();i++){
461 effects_calc->free_effect(i);
462 }
463 //this->reportFFBStatus.effectBlockIndex = 1;
464 this->reportFFBStatus.status = (HID_ACTUATOR_POWER) | (HID_ENABLE_ACTUATORS);
465 used_effects = 1;
466}
static void logSerialDebug(std::string string)
Send a log formatted sequence if debug is on.
virtual void fxUpdateEvent()
virtual void cfUpdateEvent()
std::array< FFB_Effect, EffectsCalculator::max_effects > & effects
Definition: HidFFB.h:46
reportFFB_status_t reportFFBStatus
Definition: HidFFB.h:67
void set_effect_operation(FFB_EffOp_Data_t *report)
Definition: HidFFB.cpp:383
void set_FFB(bool state)
Definition: HidFFB.cpp:188
void set_constant_effect(FFB_SetConstantForce_Data_t *effect)
Definition: HidFFB.cpp:227
void start_FFB()
Definition: HidFFB.cpp:174
uint16_t hidGet(uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) override
Definition: HidFFB.cpp:151
FFB_BlockLoad_Feature_Data_t blockLoad_report
Definition: HidFFB.h:64
void setDirectionEnableMask(uint8_t mask)
Definition: HidFFB.cpp:38
uint16_t used_effects
Definition: HidFFB.h:62
std::shared_ptr< EffectsCalculator > effects_calc
Definition: HidFFB.h:45
uint8_t directionEnableMask
Definition: HidFFB.h:61
void set_gain(uint8_t gain)
Definition: HidFFB.cpp:195
void sendStatusReport(uint8_t effect)
Definition: HidFFB.cpp:55
virtual ~HidFFB()
Definition: HidFFB.cpp:31
void set_condition(FFB_SetCondition_Data_t *cond)
Definition: HidFFB.cpp:350
FFB_PIDPool_Feature_Data_t pool_report
Definition: HidFFB.h:65
void set_envelope(FFB_SetEnvelope_Data_t *report)
Definition: HidFFB.cpp:422
bool getFfbActive()
Definition: HidFFB.cpp:43
void ffb_control(uint8_t cmd)
Definition: HidFFB.cpp:205
static bool HID_SendReport(uint8_t *report, uint16_t len)
Definition: HidFFB.cpp:47
void new_effect(FFB_CreateNewEffect_Feature_Data_t *effect)
Definition: HidFFB.cpp:240
void reset_ffb()
Definition: HidFFB.cpp:459
bool ffb_active
Definition: HidFFB.h:63
void hidOut(uint8_t report_id, hid_report_type_t report_type, const uint8_t *buffer, uint16_t bufsize) override
Definition: HidFFB.cpp:76
HidFFB(std::shared_ptr< EffectsCalculator > ec, uint8_t axisCount)
Definition: HidFFB.cpp:15
void set_ramp(FFB_SetRamp_Data_t *report)
Definition: HidFFB.cpp:435
void stop_FFB()
Definition: HidFFB.cpp:181
void set_effect(FFB_SetEffect_t *effect)
Definition: HidFFB.cpp:277
uint8_t axisCount
Definition: HidFFB.h:69
void set_filters(FFB_Effect *effect)
Definition: HidFFB.cpp:200
void set_periodic(FFB_SetPeriodic_Data_t *report)
Definition: HidFFB.cpp:445
void registerHidCallback()
static struct @612 data
hid_report_type_t
HID Request Report Type.
Definition: hid.h:85
static TU_ATTR_ALWAYS_INLINE bool tud_hid_report(uint8_t report_id, void const *report, uint16_t len)
Definition: hid_device.h:97
uint8_t const * buffer
Definition: midi_device.h:100
uint32_t bufsize
Definition: midi_device.h:95
static void * memcpy(void *dst, const void *src, size_t n)
Definition: ringbuffer.c:8
uint16_t negativeSaturation
Definition: ffb_defs.h:254
int16_t negativeCoefficient
Definition: ffb_defs.h:252
int16_t positiveCoefficient
Definition: ffb_defs.h:251
uint16_t positiveSaturation
Definition: ffb_defs.h:253
uint16_t period
Definition: ffb_defs.h:284
bool useEnvelope
Definition: ffb_defs.h:293
uint16_t startDelay
Definition: ffb_defs.h:290
int16_t startLevel
Definition: ffb_defs.h:278
uint8_t type
Definition: ffb_defs.h:274
bool useSingleCondition
Definition: ffb_defs.h:294
uint8_t gain
Definition: ffb_defs.h:276
uint32_t duration
Definition: ffb_defs.h:285
int16_t magnitude
Definition: ffb_defs.h:277
uint32_t attackTime
Definition: ffb_defs.h:287
float axisMagnitudes[MAX_AXIS]
Definition: ffb_defs.h:280
FFB_Effect_Condition conditions[MAX_AXIS]
Definition: ffb_defs.h:282
uint16_t attackLevel
Definition: ffb_defs.h:286
int16_t endLevel
Definition: ffb_defs.h:279
uint32_t startTime
Definition: ffb_defs.h:291
uint32_t fadeTime
Definition: ffb_defs.h:287
uint16_t samplePeriod
Definition: ffb_defs.h:292
int16_t offset
Definition: ffb_defs.h:275
uint16_t fadeLevel
Definition: ffb_defs.h:286
int16_t phase
Definition: ffb_defs.h:283