apply non-linear steering curve

This commit is contained in:
2026-01-18 18:58:06 +01:00
parent 218e852f06
commit 2764e18e37
3 changed files with 56 additions and 10 deletions

View File

@@ -39,6 +39,13 @@ static int mode = G29_MODE_MEDIA;
module_param(mode, int, 0444);
MODULE_PARM_DESC(mode, "Mapping mode (0=MEDIA)");
/* Steering curve exponent (100 = linear, 200 = squared, 150 = ^1.5)
* Higher values reduce sensitivity at low steering angles.
*/
static int steer_curve = 200;
module_param(steer_curve, int, 0644);
MODULE_PARM_DESC(steer_curve, "Steering sensitivity curve (100=linear, 200=squared, default=200)");
struct g29_keymap_edge {
u32 mask;
unsigned short keycode;
@@ -85,21 +92,59 @@ static void g29_steer_timer_fn(struct timer_list *t) {
struct g29_dev *g29 = timer_container_of(g29, t, steer_timer);
const int rot = le16_to_cpu(g29->last.rot_le);
int distance_from_center = abs(rot - WHEEL_CENTER);
bool press_key;
int distance_to_center = abs(rot - WHEEL_CENTER);
int adjusted_distance;
//pr_info("g29: rot=%d distance_to_center=%d\n", rot, distance_to_center);
/* Apply non-linear steering curve:
* adjusted = (distance / MAX)^(curve/100) * MAX
*
* For curve=200 (squared): 5% input -> 0.25% output, 50% -> 25%, 100% -> 100%
* For curve=150 (^1.5): 5% input -> ~1.1% output, 50% -> ~35%, 100% -> 100%
* For curve=100 (linear): 5% input -> 5% output (no change)
*
* Using integer math: adjusted = (distance^2 / MAX) for curve=200
*/
if (steer_curve == 100) {
/* Linear response - no adjustment */
adjusted_distance = distance_to_center;
} else if (steer_curve == 200) {
/* Squared response - optimized integer calculation */
adjusted_distance = (distance_to_center * distance_to_center) / WHEEL_MAX_DIST;
} else {
/* Generic power curve using normalized values (0-1000 range for precision)
* normalized = (distance * 1000) / WHEEL_MAX_DIST
* Apply power approximation, then scale back
*/
int normalized = (distance_to_center * 1000) / WHEEL_MAX_DIST;
int powered;
if (steer_curve == 150) {
/* Approximate ^1.5 with (x * sqrt(x)) */
int sqrt_norm = int_sqrt(normalized * 1000);
powered = (normalized * sqrt_norm) / 1000;
} else {
/* Fallback: squared for any other value > 100 */
powered = (normalized * normalized) / 1000;
}
adjusted_distance = (powered * WHEEL_MAX_DIST) / 1000;
}
/* Phase accumulator approach:
* Accumulate the distance on each tick.
* Accumulate the adjusted distance on each tick.
* When it exceeds the max distance, press the key and wrap.
* This gives us a duty cycle of (distance / WHEEL_MAX_DIST).
* This gives us a duty cycle of (adjusted_distance / WHEEL_MAX_DIST).
*
* Examples:
* distance = WHEEL_MAX_DIST/2 (50%) -> press every 2nd tick
* distance = WHEEL_MAX_DIST (100%) -> press every tick
* distance = WHEEL_MAX_DIST/4 (25%) -> press every 4th tick
* Examples (with curve=200):
* 5% steering -> ~0.25% press rate
* 50% steering -> 25% press rate
* 100% steering -> 100% press rate (every tick)
*/
g29->phase_accumulator += distance_from_center;
g29->phase_accumulator += adjusted_distance;
bool press_key;
if (g29->phase_accumulator >= WHEEL_MAX_DIST) {
g29->phase_accumulator -= WHEEL_MAX_DIST;
press_key = true;

View File

@@ -35,6 +35,7 @@
#define G29_BTN_RED_CW 0x02000000u
#define G29_BTN_RED_CCW 0x04000000u
#define G29_BTN_RETURN 0x08000000u
#define G29_BTN_PS3_LOGO 0xF0000000u
#define G29_DPAD_MASK 0x0000000Eu
#define G29_DPAD_UP 0x00000000u

View File

@@ -45,7 +45,7 @@ Logitech G29 USB Protocol
- `0x02000000` - Red rotation clockwise
- `0x04000000` - Red rotation counterclockwise
- `0x08000000` - Return
- `0xF0000000` - ?
- `0xF0000000` - Playstation 3 Logo Button (verify)
- `Rot`: Wheel rotation (little-endian). `0x0000` (leftmost) - `0xFFFF` (rightmost).
- `Gas`: Gas pedal. `0xFF` (up, default) - `0x00` (down).
- `Brk`: Brake pedal. `0xFF` (up, default) - `0x00` (down).