vsulako / AFFBWheel

Arduino based racing wheel controller with force feedback
MIT License
98 stars 19 forks source link

AFFBWheel (Arduino Force FeedBack Wheel)

Russian Federation Описание на русском

This is project of Arduino based wheel controller with force feedback. To configure the controller parameters, you need to use the graphical interface AFFBWheelGUI

Unfortunately, there isn't much information about conditional FFB effects, my implementation can be incorrect.
However, modern games typically use only constant force to emulate other effects.

Hardware:

Project uses USB HID communication code from VNWheel project (great thanks to hoantv's work).

Instructions for the firmware

  1. Download the project from github as zip:

    And unzip the archive to any convenient folder.
  2. Download Arduino IDE. Version 1.8.19 is recommended, later versions may have errors.
  3. Download additional libraries:
  4. Install the downloaded libraries:
    • 4.1. Download the library as zip.
    • 4.2. Open Arduino IDE, then click Sketch > Include Library > Add .ZIP Library.... and select zip with downloaded library. Instruction in English:
  5. Open the folder where you unpacked the project archive, go to the AFFBWheel folder and open the AFFBWheel.ino file in Arduino IDE.
  6. Make the necessary changes to the config.h file for customization.
  7. Connect the Arduino board to your PC.
  8. Select your board type Arduino Tools > Board (Leonardo, ProMicro, Micro, etc.):
  9. Select the port on which Arduino is detected Tools > Port:
  10. Click the upload button:
  11. Wait for the firmware process to finish. All is ready!

Wiring diagram:

The connection diagram is shown on the example of Arduino Pro Micro, for the rest of the boards, the pinout is exactly the same with the same numbers. There are slight differences for Arduino Leonardo in the location of pins on the board.

There are also several options motor driver connection.

There are two separate lines of shift registers, 16 buttons each.
Thus, 16 buttons can be placed on wheel, and 16 more on wheelbase or gearbox.

Decoupling

For lowering noise it is recommended to add decoupling capacitors (ceramic 100nf...1uf between VCC and GND) to all bare IC's (TLE5010, 74HC165, 74HC4052, MCP2304...) close as possible to IC. Modules already have these.

Encoder PPR

Set encoder PPR in config.h, in line #define ENCODER_PPR 400.

Blocking unneeded analog axes.

If analog axis pin is not connected to potentiometer, axis will produce noise. To avoid that, either pull axis pin to GND with 1-10kOhm resistor, or disable axis output with command axisdisable. So, axis will always report same value and will not mess up when detecting axes in games.
Also, 4 additional axes (AUX1-AUX4) can be disabled in config.h. Just comment out line #define PIN_AUXN ...
Disabled axis will be excluded from polling (saving ~45us of computing time) and will always report 0%.

If a game does not support DIY wheels

You can try to use controller emulator GIMX. You will need an Atmega32U4 board (Leonardo/promicro/teensy2/etc) and CP1202 USB-UART adapter.

Alternate hardware configurations.

Testing software:

Configuration.

Configuration options are in config.h, mostly for different hardware configurations (see below).

Settings and commands.

Changeable settings can be changed on the fly using GUI or commands on serial port.
For most commands, if no value is given, command prints current setting.

Alternate hardware configurations.

Misc:

Leonardo instead of ProMicro:

On Leonardo board pins 14,15,16(MISO, SCK, MOSI - for SPI) are placed on ICSP connector.
All connections are same as for ProMicro.

Motor control.

There are 3 variants of BTS7960 connection:

1) this is shown on main diagram. EN pins of BTS7960 are connected with diodes.

Motor is online only in duty part of PWM period, which gives "softer" force feedback.

2) EN pins are controlled by separate wire.

Here motor is online when FFB is active. FFB feels harder and more powerful than in variant #1, but wheel becomes "heavier".
This variant requires to uncomment #define MOTOR_ENABLE_PIN and set a pin to use.

3) EN pins are permanently connected to VCC.

FFB feels like variant #2, but motor is always online, which leads to constant "load" of wheel. On other hand, free pin is not needed.

Hardware (motors, reductors, etc...) and preferences are different for different people, so try all variants and choose what suits you better.

Checking rotation directions for wheel sensor and FFB motor.

First, check the wheel sensor, with FFB motor power turned off.

Wheel sensor readings should increase when wheel is turned clockwise. (there is a schematic wheel drawing in GUI, it should rotate just like real wheel)
If it rotates in wrong direction, it means that wheel sensor direction is wrong.

FFB can be checked with FEdit application. Create a weak constant force effect, and check if it works right.

Detailed description:

Alternate options for steering axis:

TLE5010:

TLE5010 is a digital sensor that allows to get angle of magnetic field rotation. It can be used instead of encoder.

Wiring diagram:

full schematics

Placement of magnet and TLE5010:

Magnet is placed at top center of steering axis, TLE5010 is placed against it, airgap is 1-3 mm. Magnet pole separating line must be faced to TLE5010.

Changes in config.h:

Include libraries:

AS5600

AS5600 - 12-bit digital magnet rotation sensor with I2C interface. It is used similar to TLE5010.

Wiring diagram:

Remove resistor R1 (0ohm) if powering module with 5v.

Changes in config.h:

MLX90316

Another magnet rotation sensor. Used similar to TLE5010/AS5600. It is used in Thrustmaster T500 wheel controller. (Support is experimental, because I don't have this sensor)

Connection is similar to TLE5010:

Changes in config.h:

Alternate options for analog axes (pedals):

Option #1: pullups.

This allows to use 4 wires unstead of 5. Simple and cheap, but has some disadvantages.

Wiring diagram:
Wiring diagram

Pullup resistance must be equal to axis potentiometer resistance.

Changes in config.h:

Option #2: analog multiplexer 74HC4051/74HC4052/74HC4067 + shift register 74HC164

Also allows to use 4 wires unstead of 5.

Wiring diagrams:
74HC4051
Wiring diagram for 74HC4052
Wiring diagram for 74HC4067

Changes in config.h:

Option #3: external ADC MCP3204

It may be worthwhile to use ADC (analog-digital converter) for pedals. Pedals require connecting with long cable, which can catch interference(noise). Digital signal from ADC, unlike analog one, is insensitive to this. Also, external ADC can provide better resolution than 10-bit one in Arduino.

MCP3204 is fast enough 12-bit 4-channel ADC with SPI interface(6 wires). However, communication can be fit into 5 and even 4 wires.

Wiring diagrams:
6 wires
5 wires
4 wires v1
4 wires v2
4 wires v3

In case of using TLE5010 wires are connected in parallel:
6 wires + TLE5010
5 wires + TLE5010
4 wires v1 + TLE5010
4 wires v2 + TLE5010

Changes in config.h:

Option #4: external I2C ADC ADS1015

Wiring diagram: ADS1015

This ADC is relatively slow (~0.3ms per conversion), therefore axes will be read one per loop, resulting in 3x lower reading rate.
Also, it has fixed voltage references (±2.048V is used), so with 5v VCC potentiometers will have ~10% deadzones at min and max. If you need to use full potentiometer range, it can be compensated by adding couple of resistors(wiring diagram) to each potentiometer (start with resistance = 1/10 of potentiometer resistance)
Another option is to provide 4.1v voltage for potentiometers (e.g. with TL431 - wiring diagram).

Changes in config.h:

Option #5: external I2C ADC ADS7828

Another I2C ADC. 12bit, 8 channel, fast enough, cheap enough, has external voltage reference. TSSOP-16 package is tiny, but with some effort it can be soldered by hand on breakout board.

ADS7828

Wiring:

ADS7828

Changes in config.h:

Alternate options for buttons.

Option #1: 74HC165 4-wire

It is possible to get rid of PL wire (from 74HC165 to Arduino), and use only 4 wires.

Wiring diagram:
Wiring diagram

Changes in config.h:

Option #2: I2C extenders MCP23017

Wiring diagram:
Wiring diagram

Changes in config.h:

Option #3: Shift registers CD4021B

CD4014B seems to be compatible (not tested in hardware, because I don't have any).

Connections are similar to 74HC165, but chip pinout is different:

Wiring diagram

Getting rid of PL line for 4-wire connections is also possible:

Wiring diagram

Changes in config.h:

Option #4: I2C expanders PCF8574 or PCF8575

Wiring diagrams:

It is possible to mix 1 x PCF8575 and 2 x PCF8574's (example: 16 buttons for wheel, 8 buttons for wheelbase/buttonbox, 8 buttons for H-shifter).

Each board must have unique i2c address.

On PCF8574 boards address is set by switches,jumpers or pins A0-A2.
Boards with switches has reverse switch order: switch 1 controls A2, switch 3 controls A0.
On PCF8575 boards address is set by solder jumpers A0-A2. Jumper consists of 3 solder pads, middle one must be connected either to GND (right on pictire), or to VCC (left).

On "square" PCF8575 boards (red on picture) there are no I2c pullup resistors.
If there are no other I2c devices connected, resisors 1-10kOhm must be installed: from SCL to VCC and from SDA to VCC (shown on connection diagram). R1/R2 pads can be used for this, but this requires SMD resistors 0603.

For some reason "long" PCF8575 boards (blue on picture) worked incorrectly in my case (had no pullips in input mode, required external pullups for buttons, may be defect), so cannot recommend them.

Address reference: (0 - GND, 1 - VCC)

Address A2 A1 A0
0x20 0 0 0
0x21 0 0 1
0x22 0 1 0
0x23 0 1 1
0x24 1 0 0
0x25 1 0 1
0x26 1 1 0
0x27 1 1 1

Changes in config.h:

In case of using another I2C devices (AS5600,ADS1015...) - connect in parallel to same pins.

Additional features

Wheel sensor with mechanical transmission

Steering wheel position sensor can be placed aside of wheel axis and connected to it through mechanical transmission (gears, belt...).
If transmission ratio is not 1:1, the correction coefficient must be applied.
It is possible with these parameters in config.h:

#define STEER_TM_RATIO_ENABLED  //Uncomment to enable feature
#define STEER_TM_RATIO_MUL      //Multiplication factor
#define STEER_TM_RATIO_DIV      //Division factor

Sensor readings are multiplied by STEER_TM_RATIO_MUL and divided by STEER_TM_RATIO_DIV.

Example: wheel and sensor axes are connected with belt transmission, wheel pulley diameter - 200 units, sensor pulley diameter - 20 units, with one turn of wheel sensor's axis makes 10 turns, correction coefficient is 20:200, or 1:10, and configuration is:

#define STEER_TM_RATIO_ENABLED
#define STEER_TM_RATIO_MUL 1
#define STEER_TM_RATIO_DIV 10

If rotation direction is changed in transmission (geared transmission, or magnetic sensor is placed on another end of axis) - one of numbers can be specified as negative:

#define STEER_TM_RATIO_ENABLED
#define STEER_TM_RATIO_MUL 1
#define STEER_TM_RATIO_DIV -10

Numbers can be fractional.

Buttons on analog pin

There is possibility to use multiple buttons, connected to single analog arduino pin.

Wiring diagrams:

Wiring diagram

Principle: when button is pressed, analog pin voltage is changed.

Advantage: only 3 or 2 wires can be used to connect multiple buttons.
Disadvantage: only one button can be pressed simultaneously, so only use case is for gear shifter.

Configuration example:

#define APB                     //uncomment to enable feature
#define APB_PIN        A11      //analog pin
#define APB_BTN_COUNT  2        //number of buttons connected
#define APB_VALUES     32,96    //ADC values (0-255) for each button
#define APB_TOLERANCE  10       //tolerance (plus-minus to ADC value)
#define APB_BTNS       25,26    //numbers of redefined buttons (1-32)

This means:

Command apbout in Serial monitor will print ADC values from selected pin.

Analog H-shifter.

A H-shifter made like stick, with 2 potentiometers instead of buttons can be used.
Potentiometers are connected to unused analog pins.
H-shifter can have 6 or 8 positions.

X axis represents ADC from one potentiometer, Y axis from another.
Button N is considered pressed, if current X/Y values are in corresponding zone (gray).
X1,X2,X3,Y1,Y2 values set zone bounds. Values must be in ascending order, i.e. X1<X2<X3, Y1<Y2.
X3 is not used for 6-position config.

ADC values can be printed out by command ahsout in Serial monitor.

Configuration in config.h:

#define ASHIFTER                //uncomment to enable feature
#define ASHIFTER_PINX     A4    //analog pin for potentiometer X
#define ASHIFTER_PINY     A5    //analog pin for potentiometer Y
#define ASHIFTER_POS      8     //number of positions, 6 or 8
#define ASHIFTER_Y1       50    //zone bounds (0-255)
#define ASHIFTER_Y2       200
#define ASHIFTER_X1       64  
#define ASHIFTER_X2       128  
#define ASHIFTER_X3       192
#define ASHIFTER_1ST_BTN  25    //number of button for pos 1 (1-32)

Buttons will be redefined in series, starting from ASHIFTER_1ST_BTN.
If 8-position is used, first button is 25, shifter will use buttons #25,26,27,28,29,30,31,32.

Hat switch

Any 4 buttons can be defined for using as 8-position hat switch.

Configuration in config.h:

#define HATSWITCH           //uncomment to enable feature
#define HAT_BTN_UP     20   //button numbers for hat directions
#define HAT_BTN_DOWN   21
#define HAT_BTN_LEFT   22
#define HAT_BTN_RIGHT  23
#define HAT_CLR_BTNS        //if this line is commented, selected buttons will continue to register presses along with hat switch

Auto find range and center.

If your wheel has mechanical range limiters (mostly it is for factory-built wheels), you can enable automatic finding wheel range and center.

It works like this: Wheel will move counter-clockwise until it reaches one limiter, then it turns back clockwise until reaching another limiter.
When limiters will stop it's movement, corresponding limits will be determined, new values of range and center position calculated, and then wheel will return to center.

Do not enable it on if you do not have limiters: wheel will rotate endlessly in search of limiter.

For enabling, uncomment line #define AFC_ON in config.h

Also, set up settings:

#define AFC_FORCE   0    //Force [0...16383] to exert when finding center. 0 by default to prevent accidents.
#define AFC_PERIOD 50    //Position check period in milliseconds
#define AFC_TRESHOLD 10  //Minimum position change to detect movement (1 turn - 8192 units)
#define AFC_NORANGE      //Uncomment to disable range setting
#define AFC_RANGE_FIX 1  //range will be decreased by this value (in degrees)

More detail: If axis moves less than AFC_TRESHOLD in AFC_PERIOD time - it means it has reached limiter.
After finding both limits, wheel range in degrees will be calculated and then decreased by AFC_RANGE_FIX value.
This is needed for software limit to act before reaching real limiters, and prevent wheel from kicking into them too hard.
AFC_NORANGE - disables range setting, only center position will be set.
Also, if range found is too low (less than 2 degrees - apparently, wheel is not moving, either FFB is not working correctly, or force is not enough to move) - procedure will be aborted.

Wheels, motors, reductors differ, so parameters should be be tuned individually.
For convenience, if AFC_ON is enable, a new command will be available for Serial Monitor: autocenter <force> <period> <treshold>
It allows to test auto find center procedure with different settings without reflashing.
If parameter is not set, the value from config.h will be used.

First, make sure that wheel axis and FFB directions are set correctly.
Then, in config.h enable only AFC_ON, leave AFC_FORCE at 0.
If AFC_FORCE will be too high, and your motor/reductor is powerful - it can break either limiter or itself, so start from low force values, e.g. 3000: autocenter 3000.
If wheel is not moving - try 4000, and so on, until it moves.
It can happen that wheel will reach first limiter and stop, thinking that second one is also found.
In that case you can increase force further, or play with other two parameters.
In process, Serial monitor will output current position and distance traveled during period.
After finding appropriate values, put them to config.h to be used by default.