This post is about braking !!
I'm looking for more realistic feedbacks in order to improve my driving "performance" and increase the immersion feeling.
Considering a load-cell brake pedal, it's lacking of feedback from the wheels and in particular when they are blocking...
If I can feel in my pedal when the wheels are blocked : I could release a bit of pressure and hence being the closest to the best braking.
In the case of a car with ABS, if I can feel the rumble in my foot : it'd increase the immersion feeling !
A commercial product does this : the Fanatec CSP V2 (motor + plugin). When the wheels are blocked, a motor is vibrating.
How can I implement this in my DIY pedals with Xsim ?
I've looked for wheel's speeds or ABS activation in the sender's data (rFactor) but I haven't seen this information...
Someone has developped an external plug-in for rFactor http://www.richardjackett.com/brakemodproject
but may be (certainly) Xsim could do this natively
Thanks
Mat
___________________________________________________________________
edit : Okay, here is the result.
It not completely finalised but effective.
General setup:
so I went the ISI C++ "internal plugin" way : programming a plugin to send infos via USB to arduino
Inside the rFactor plugin, I open a serial communication directly to my Arduino nano through USB cable : no need of Xsim in this case.
directly from game plugin to Arduino board.
An .ini file is used to setup the parameters (grip level, mode...)
USB Comport selection via ini file is not effective : you have to manually force Windows to reassign your arduino on COM19 @115200
Plugin telemetry data available:
Inside the plugin example from rFactor, you can see a slip ratio :
- Code: Select all
TelemWheel
.mRotation; (float) // radians/sec
.mGripFract; (float) // an approximation of what fraction of the contact patch is sliding
so I can check the slip ratio of each wheel = mWheel[i].mGripFract
if mGripFract > 80% then the wheel is blocked and is sliping
I customised the internal rFactor plugin and I coded 2 modes:
- wheel.mGripFract above a %
- difference between wheel.mRotation and vehicle speed above a %
There are also more possibilities in the computing (still pending):
using the 4 wheel's maximum or the 4 wheels mean value or the 2 front wheels or ...
The code of plugin:
I'm using a library "serialib" to implement my serial com port
and "Readini" to parse my ini file to get some user parameters.
This code is the source. It allows you to understand what is really done inside the plugin.
This code needs to be compiled by Microsoft Visual C studio into a .ddl file. See next chapter
- Code: Select all
//ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ
//Ý Þ
//Ý Module: Internals Example Source File Þ
//Ý Þ
//Ý Description: Declarations for the Internals Example Plugin Þ
//Ý Þ
//Ý Þ
//Ý This source code module, and all information, data, and algorithms Þ
//Ý associated with it, are part of CUBE technology (tm). Þ
//Ý PROPRIETARY Þ
//Ý Copyright (c) 1996-2006 Image Space Incorporated. All rights reserved. Þ
//Ý Þ
//Ý Þ
//Ý Change history: Þ
//Ý tag.2005.11.30: created Þ
//Ý jmm.2006.01.06: modified for public Þ
//Ý Þ
//ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß
//***************************************************************************//
#include "Example.hpp" // corresponding header file
#include <math.h> // for atan2, sqrt
#include <stdio.h> // for sample output
//***************************************************************************//
//RacingMat
#include "serialib.h" // Library described above
#include "ReadIni.h"
#include <tchar.h>
#include <stdlib.h>
//create the serial communication SerialCom
serialib SerialCom;
char *cpMode; // grip lost or blocked wheel
char *cpLevel; // grip loss level
char *cpApproxDiam; // approximative wheel diameter
double level;
//***************************************************************************//
// plugin information
// Change this information to siut your needs. rFactor will use this information
// to ensure that it is using the most recent version
unsigned g_uPluginID = 2;
//RacingMat
char g_szPluginName[] = "RacingMat Brake Pedal - 2013-07-12";
//RacingMat
unsigned g_uPluginVersion = 002;
unsigned g_uPluginObjectCount = 1;
InternalsPluginInfo g_PluginInfo;
//***************************************************************************//
//***************************************************************************//
// interface to plugin information
extern "C" __declspec(dllexport)
const char* __cdecl GetPluginName() { return g_szPluginName; }
extern "C" __declspec(dllexport)
unsigned __cdecl GetPluginVersion() { return g_uPluginVersion; }
extern "C" __declspec(dllexport)
unsigned __cdecl GetPluginObjectCount() { return g_uPluginObjectCount; }
// get the plugin-info object used to create the plugin.
extern "C" __declspec(dllexport)
PluginObjectInfo* __cdecl GetPluginObjectInfo( const unsigned uIndex )
{
switch(uIndex)
{
case 0:
return &g_PluginInfo;
default:
return 0;
}
}
//***************************************************************************//
//***************************************************************************//
// InternalsPluginInfo class
InternalsPluginInfo::InternalsPluginInfo()
{
// put together a name for this plugin
sprintf( m_szFullName, "%s - %s", g_szPluginName, InternalsPluginInfo::GetName() );
}
//***************************************************************************//
//***************************************************************************//
const char* InternalsPluginInfo::GetName() const { return ExampleInternalsPlugin::GetName(); }
const char* InternalsPluginInfo::GetFullName() const { return m_szFullName; }
// Change this to suit your needs
const char* InternalsPluginInfo::GetDesc() const { return "RacingMat Brake Pedal"; }
const unsigned InternalsPluginInfo::GetType() const { return ExampleInternalsPlugin::GetType(); }
const char* InternalsPluginInfo::GetSubType() const { return ExampleInternalsPlugin::GetSubType(); }
const unsigned InternalsPluginInfo::GetVersion() const { return ExampleInternalsPlugin::GetVersion(); }
void* InternalsPluginInfo::Create() const { return new ExampleInternalsPlugin(); }
//***************************************************************************//
//***************************************************************************//
// InternalsPlugin class
const char ExampleInternalsPlugin::m_szName[] = "RacingMatBrakePedalPlugin";
const char ExampleInternalsPlugin::m_szSubType[] = "Internals";
const unsigned ExampleInternalsPlugin::m_uID = 1;
const unsigned ExampleInternalsPlugin::m_uVersion = 1;
//***************************************************************************//
//***************************************************************************//
PluginObjectInfo *ExampleInternalsPlugin::GetInfo() { return &g_PluginInfo; }
//***************************************************************************//
//***************************************************************************//
void ExampleInternalsPlugin::Startup()
{
// Open ports, read configs, whatever you need to do.
// Read the windows COM Port of the Arduino
// in the ini file
// default enabled to true
mEnabled = true;
int Ret; // Used for return values
// path of the ini file (same directory as this plugin)
LPCSTR localPath="Plugins\\BrakePlugin.ini";
char *cpComPort;
char *cpBaudRate;
// read a string value of a key in a section of the ini file
// section key default value ini file path
cpComPort=ReadKeyString(_T("Serial"), _T("Port"), _T("\\\\.\\COM6"), _T(localPath));
// Beware !! if the address is given by the ReadKey function (from ini file), no double \\ before com port address
// inside the ini file, only \\ ? or \\\\ ?
cpBaudRate=ReadKeyString(_T("Serial"), _T("Baudrate"), _T("115200"), _T(localPath));
// Open serial link Com Port at BaudRate
// Ret=SerialCom.Open(cpComPort,atof(cpBaudRate));
// Ret=SerialCom.Open(cpComPort,115200);
Ret=SerialCom.Open("\\\\.\\COM19",115200);
cpMode=ReadKeyString(_T("Parameters"), _T("Mode"), _T("1"), _T(localPath));
cpLevel=ReadKeyString(_T("Parameters"), _T("Level"), _T("0.6"), _T(localPath));
level = atof(cpLevel);
cpApproxDiam=ReadKeyString(_T("Parameters"), _T("ApproxDiam"), _T("0.5"), _T(localPath));
}
//***************************************************************************//
//***************************************************************************//
void ExampleInternalsPlugin::EnterRealtime()
{
// start up timer every time we enter realtime
mET = 0.0f;
}
//***************************************************************************//
//***************************************************************************//
void ExampleInternalsPlugin::UpdateTelemetry( const TelemInfo &info )
{
const unsigned short cusBufferSize = 1024;
char* cpTemp = new char[cusBufferSize];
const float metersPerSec = sqrtf( ( info.mLocalVel.x * info.mLocalVel.x ) +
( info.mLocalVel.y * info.mLocalVel.y ) +
( info.mLocalVel.z * info.mLocalVel.z ) );
//RacingMat
int noGripWheels = 0;
int lockedWheels = 0;
float approxDiam = 0.5 ; // toDo convert char cpApproxDiam into float
// double level = 0.80; // toDo convert char cpLevel into float
// level = 0.80 -> wheels must have less than 80% of the grip
char* vibrate;
int Ret;
vibrate="0"; // false
if ((metersPerSec>0.1) & (info.mUnfilteredBrake >= 0.4 ))
{
if (cpMode="1")
{
for( long i = 0; i < 4; ++i )
{ // for each wheel
// Wheels (i==0)"FrontLeft"
// (i==1) "FrontRight"
// (i==2) "RearLeft"
// (i==3) "RearRight"
const TelemWheel &wheel = info.mWheel[i];
// count how many wheels are blocked
// check if wheel rotates less than expected by the vehicule speed (minus 50%)
if ((3.14*approxDiam*abs(wheel.mRotation))<(0.50*metersPerSec)) { ++lockedWheels; }
}
if (lockedWheels>=2)
vibrate="1"; //true
} else
{
for( long i = 0; i < 4; ++i )
{ // for each wheel
// Wheels (i==0)"FrontLeft"
// (i==1) "FrontRight"
// (i==2) "RearLeft"
// (i==3) "RearRight"
const TelemWheel &wheel = info.mWheel[i];
// count how many wheels lost its grip
if (wheel.mGripFract<level) { ++noGripWheels; } // coupler avec freinage en cours et avec vitesse rel non nulle
}
//envoie le signal perte de grip ou non
if (noGripWheels>=2)
vibrate="1"; //true
}
}
Ret=SerialCom.WriteString(vibrate); // Send the command on the serial port
if(cpTemp)
delete [] cpTemp;
}
//***************************************************************************//
//***************************************************************************//
void ExampleInternalsPlugin::UpdateGraphics( const GraphicsInfo &info )
{
//
}
//***************************************************************************//
//***************************************************************************//
bool ExampleInternalsPlugin::CheckHWControl( const char * const controlName, float &fRetVal )
{
return( false );
}
//***************************************************************************//
//***************************************************************************//
bool ExampleInternalsPlugin::ForceFeedback( float &forceValue )
{
return( false );
}
//***************************************************************************//
The code of .ini file:
- Code: Select all
[Serial]
Baudrate=115200
Port=\\.\\COM19
[Parameters]
; Mode=1 is blocked wheels
; Mode=2 is grip loss
Mode=1
;level of grip loss
Level=0.4
;approximative diameter of wheels
; if greater, vibration will arise sooner
ApproxDiam=0.6
The .dll and .ini files:
the .dll and .ini files have to be put into the rfactor plugin directory
Arduino code:
extremely simple
for debugging/tuning purpose, you can light a LED
but next, just plug your relay instead of LED to drive the solenoid.
- Code: Select all
int ledPin = 13; // Sortie où sera branchée la LED
int ledState = LOW; // Etat de la LED (LOW par défaut)
void setup() {
Serial.begin(115200); // On initialise la connexion
pinMode(ledPin, OUTPUT); // Et la sortie de la LED
}
void loop() {
int received; // Variable servant à récupérer
// les données reçues
if (Serial.available()>0) { // Si des données sont disponibles
received = Serial.read(); // On les récupère
if(received == '1') // Si "0" est reçu
ledState = HIGH ; // On éteinds la LED
else // Sinon
ledState = LOW; // On l'allume
digitalWrite(ledPin, ledState); // Enfin on change l'état de la LED
}
}
The actuator:
the solenoid is the best, the most reactive! compared to excentric DC motors
This solenoid comes from a steptronic
Here are the results of my tests of driving Solenoid from Arduino: