Load Cell Sim Racing Handbrake

I finally got ’round-tuit. I’d picked up a 10kg load cell and HX711 load cell amplifier from SparkFun a couple of years ago, but as is often the case, multiple other shiny new projects grabbed my attention. The lack of an easy way to fabricate many of the parts had something to do with not getting ’round-tuit as well.

So, now that I’ve a 3D printer, I decided it was time to retire the trusty old handbrake I’d built back in 2019 and build a new one.

I soldered up the HX711, a HiLetgo Pro Micro Arduino clone (Arduino Leonardo board in the IDE), and a header for the load cell onto protoboard… and made the first mistake.

The HX711 ships jumpered to run at 10 Hz, which is fine for a scale, but way too slow for a handbrake. See that trace right above where it says “RATE”. Well, it needs to be cut to run the HX711 at 80 Hz. So, rather than desolder everything, I very carefully drilled a hole through the back of the protoboard and cut the trace. Don’t do it this way – much easier to cut it first. <g>

Next, I designed a test base, attached the load cell to a piece of wood, and bolted it to the rig to see if it was strong enough. Nothing broke, so I proceeded to write the code.

Testing setup

The code is nothing fancy, just used the MHeironimus Arduino joystick library and the bogde HX71 libraries. Read data, scale it, ignore low level noise, and send to the PC when the value changes.

With the code and the test base working I got to designing the final base and lever. The final base included a place to mount the PCB.

Base with PCB mount

Rather than use a spring, I printed a block of TPU which goes in front of the lever. I printed a few, changing the print density until I liked the amount of squish it provided.

Test rig

The Lever

The lower part of the printed lever has a pair of press fit 608 skateboard bearings on either side to spread the load. The groove down the side is for the load cell wiring. The opening to the inside is wide enough to slip the five pin Dupont connector through.

Press fit bearings in vise

I decided the first upper lever was a bit too bulky and a bit too tall, so I made another with some taper that was a bit shorter.

Original upper lever

Tape covers wire – will hot glue later

Slimmer upper handle

The holes on the countersink side are supposed to be clearance holes. Depending on how it prints, you may have to chase them with a reamer or drill. The holes on the far side from the countersinks are supposed to be tight enough that they are threaded by the screws.

Designed and printed a grip in TPU. The grip for the first lever had a domed top, which I decided I didn’t care for, so I made the grip for the slim lever flat on top.

Did not like the domed top

Code

The code is quite simple. All the heavy lifting is done in the joystick and HX711 libraries. You can get a copy here – Analog-E-Brake-Load-Cell

You may be happy with the settings I used for SCALE_FACTOR and NOISE, but to determine these yourself just build with DEBUG and CALIBRATE defined. I’d initially set NOISE to 10, but discovered that the vibration of the rig while driving was enough to generate readings as high as 150, so I bumped that up to 250.

// Analog eBrake-Handbrake using hx711 to read load cell

// #define DEBUG
// #define CALIBRATE

#include "Joystick.h"	// https://github.com/MHeironimus/ArduinoJoystickLibrary
#include "HX711.h" 		// https://github.com/bogde/HX711

#define LOADCELL_DOUT_PIN  3
#define LOADCELL_SCK_PIN  2

HX711 scale;

// max joystick (32767) divided by max HX711 reading (1,000,000) by experiment
#define SCALE_FACTOR	0.032767

// noise level determined by experiment, DEBUG & CALIBRATE defined
#define NOISE 		250

#define BUTTONS		0
#define HATS		0

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_JOYSTICK, BUTTONS, HATS,
	false, false, false, false, false, false, false, false, false, true, false);
//  X      Y      Z      Rx     Ry     Rz     Rudder Throt  Accel  Brake  Steering

long offset;

void setup()
{
	Joystick.setBrakeRange(0, 32767);
	Joystick.begin();
	#if defined(DEBUG)
	Serial.begin(115200);
	#endif

	scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);

	offset = scale.read_average(10);	// zero out offset at startup

	Joystick.setBrake(0);		// start at zero brake
}

#if defined(CALIBRATE)
	long reading = 0;
#endif

long lastVal = 0;

void loop()
{
	if (scale.is_ready()) {
		long mapped = (long)((float) (abs(scale.read() - offset)) * SCALE_FACTOR);

		if (mapped > 32767)
			mapped = 32767;

		#if defined(CALIBRATE)
		/* calibration:
		 NOISE
		 Power up with no pressure on the cell, wait a bit and record the noise level. Note
		  that it is very sensitive - it will pick up vibrations of your rig. You might wish
		  to drive a bit with the calibration code running. Update NOISE value.

		 SCALE_FACTOR
		 Apply reasonable pressure on lever. If it maxes out (32767) too easily change
 		 SCALE_FACTOR to bring it in range.
		*/
		if (mapped > reading) {
			reading = mapped;
			Serial.print("Value: ");
			Serial.println(reading);
		}
		#else
		if (mapped <= NOISE)
			mapped = 0;
		if (lastVal != mapped) {		// don't transmit data unless changed
			Joystick.setBrake(mapped);
			lastVal = mapped;
		}
		#endif
	} else {
		delay(1);
	}
}

Parts

  • 60 x 40 mm proto PCB
  • HiLetgo Arduino Pro Micro clone (https://tinyurl.com/3md9wk75)
  • HX711 load cell amplifier (https://www.sparkfun.com/products/13879)
  • 2 608 roller skate bearings
  • 10 Kg load cell (https://www.sparkfun.com/products/13329)
  • 2″ 5/16 bolt and nut
  • 2 M4x30 screws
  • 2 M5x30 screws

Model Files

The model files are available at Printables.com

The 33 Button Box with encoders has grown up… It’s now 41!

Still just as unexciting to look at, but with more buttons!

I just saw an ad for one of the commercial button boxes and they listed a great new feature – get more outputs from the rotary encoders by using their push buttons as shift keys. A great addition to my original button box.

As I’d recently been thinking about wanting more rotary encoders and having more buttons than I knew what to do with, I thought it was a great idea to sacrifice four of the buttons to get four more rotary encoders.

It was just a simple matter of tracking the state of the four buttons and using that info to send different codes for the encoders if they were pressed. I’m still sending the button presses as well, so I can still choose whether to use the extra encoders or the buttons when I assign the buttons to controls in game.

There’s not all that much to doing a button box, so I’ll just publish the code here.

//BUTTON BOX
//USE w ProMicro
//Tested in WIN10 + Assetto Corsa
//AMSTUDIO
//20.8.17
// YouTube video: https://www.youtube.com/watch?v=Z7Sc4MJ8RPM

// 10/06/2019
// Bill Smithem
// Added additional button
// 4/4/2020
// Bill Smithem
// set debounce time to 50 (default was 10)
// 01/21/2021
// Bill Smtihem
// add "4 func" rotaries - different code sent if button pressed while turning. Tracked state of buttons 21-24
//  and used that to "shift" the rotary encoders. Note: we're still sending the button presses for the rotary
//  encoders, so mind that when assigning buttons in game.
// Number of "buttons" goes from 33 to 41

#include <Arduino.h>
#include <Keypad.h>
#include <Joystick.h>

#define ENABLE_PULLUPS
#define NUMROTARIES 4
#define NUMBUTTONS 25
#define NUMROWS 5
#define NUMCOLS 5

// Odd map is so buttons come out in numerical order, not scanning order. i.e. top left button is one, next is
//  two, etc. Just wired in the most convenient fashion and worked it out in the map.
byte buttons[NUMROWS][NUMCOLS] = {
  {5, 6, 17, 23, 12},
  {3, 4, 16, 22, 11},
  {9, 10, 19, 20, 14},
  {1, 2, 15, 21, 0},
  {7, 8, 18, 24, 13},
};

struct rotariesdef {
  byte pin1;
  byte pin2;
  int ccwchar;      // send this for CCW
  int cwchar;       //  this for CW
  int ccwPchar;     //  this for CCW with button pressed
  int cwPchar;      //  and this for CW with button pressed
  volatile unsigned char state;
};

rotariesdef rotaries[NUMROTARIES] {
  {0, 1, 32, 31, 40, 39, 0},
  {2, 3, 29, 30, 38, 37, 0},
  {4, 5, 27, 28, 36, 35, 0},
  {6, 7, 25, 26, 34, 33, 0},
};

#define DIR_CCW 0x10
#define DIR_CW 0x20
#define R_START 0x0

#ifdef HALF_STEP
#define R_CCW_BEGIN 0x1
#define R_CW_BEGIN 0x2
#define R_START_M 0x3
#define R_CW_BEGIN_M 0x4
#define R_CCW_BEGIN_M 0x5
const unsigned char ttable[6][4] = {
  // R_START (00)
  {R_START_M,            R_CW_BEGIN,     R_CCW_BEGIN,  R_START},
  // R_CCW_BEGIN
  {R_START_M | DIR_CCW, R_START,        R_CCW_BEGIN,  R_START},
  // R_CW_BEGIN
  {R_START_M | DIR_CW,  R_CW_BEGIN,     R_START,      R_START},
  // R_START_M (11)
  {R_START_M,            R_CCW_BEGIN_M,  R_CW_BEGIN_M, R_START},
  // R_CW_BEGIN_M
  {R_START_M,            R_START_M,      R_CW_BEGIN_M, R_START | DIR_CW},
  // R_CCW_BEGIN_M
  {R_START_M,            R_CCW_BEGIN_M,  R_START_M,    R_START | DIR_CCW},
};
#else
#define R_CW_FINAL 0x1
#define R_CW_BEGIN 0x2
#define R_CW_NEXT 0x3
#define R_CCW_BEGIN 0x4
#define R_CCW_FINAL 0x5
#define R_CCW_NEXT 0x6

const unsigned char ttable[7][4] = {
  // R_START
  {R_START,    R_CW_BEGIN,  R_CCW_BEGIN, R_START},
  // R_CW_FINAL
  {R_CW_NEXT,  R_START,     R_CW_FINAL,  R_START | DIR_CW},
  // R_CW_BEGIN
  {R_CW_NEXT,  R_CW_BEGIN,  R_START,     R_START},
  // R_CW_NEXT
  {R_CW_NEXT,  R_CW_BEGIN,  R_CW_FINAL,  R_START},
  // R_CCW_BEGIN
  {R_CCW_NEXT, R_START,     R_CCW_BEGIN, R_START},
  // R_CCW_FINAL
  {R_CCW_NEXT, R_CCW_FINAL, R_START,     R_START | DIR_CCW},
  // R_CCW_NEXT
  {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
};
#endif

byte rowPins[NUMROWS] = {21, 20, 19, 18, 15};
byte colPins[NUMCOLS] = {14, 16, 10, 9, 8};

Keypad buttbx = Keypad( makeKeymap(buttons), rowPins, colPins, NUMROWS, NUMCOLS);

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,
                   JOYSTICK_TYPE_JOYSTICK, 41, 0,
                   false, false, false, false, false, false,
                   false, false, false, false, false);

void setup() {
  void rotary_init();

  Joystick.begin();
  rotary_init();
  buttbx.setDebounceTime(50);
}

void loop() {
  void CheckAllEncoders();
  void CheckAllButtons();

  CheckAllEncoders();

  CheckAllButtons();

}

// keep state of last four buttons (ones attached to rotaries)
KeyState rbState[] = { RELEASED, RELEASED, RELEASED, RELEASED };

void CheckAllButtons(void) {
  int kc;

  if (buttbx.getKeys())
  {
    for (int i = 0; i < LIST_MAX; i++)
    {
      if ( buttbx.key[i].stateChanged )
      {
        kc = buttbx.key[i].kchar - 21;        // track state of buttons 21-24 (switches on rotaries)
        switch (buttbx.key[i].kstate) {
        case PRESSED:
        case HOLD:
          Joystick.setButton(buttbx.key[i].kchar, 1);
          if (kc >= 0)
            rbState[kc] = PRESSED;
          break;
        case RELEASED:
        case IDLE:
          Joystick.setButton(buttbx.key[i].kchar, 0);
          if (kc >= 0)
            rbState[kc] = RELEASED;
          break;
        }
      }
    }
  }
}

void rotary_init() {
  for (int i = 0; i < NUMROTARIES; i++) {
    pinMode(rotaries[i].pin1, INPUT);
    pinMode(rotaries[i].pin2, INPUT);
#ifdef ENABLE_PULLUPS
    digitalWrite(rotaries[i].pin1, HIGH);
    digitalWrite(rotaries[i].pin2, HIGH);
#endif
  }
}

unsigned char rotary_process(int _i) {
  unsigned char pinstate = (digitalRead(rotaries[_i].pin2) << 1) | digitalRead(rotaries[_i].pin1);
  rotaries[_i].state = ttable[rotaries[_i].state & 0xf][pinstate];
  return (rotaries[_i].state & 0x30);
}

void CheckAllEncoders(void) {
  for (int i = 0; i < NUMROTARIES; i++) {
    unsigned char result = rotary_process(i);
    if (result == DIR_CCW) {
      // check if button pressed
      if (rbState[3-i] == PRESSED) {
        Joystick.setButton(rotaries[i].ccwPchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwPchar, 0);
      } else {
        Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
      }
    }
    if (result == DIR_CW) {
      if (rbState[3-i] == PRESSED) {
        Joystick.setButton(rotaries[i].cwPchar, 1); delay(50); Joystick.setButton(rotaries[i].cwPchar, 0);
      } else {
        Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
      }
    }
  }
}

World’s Cheapest Router Plane

Home brew router plane

I was chiseling out a recess for an LED RPM display for my sim rig and realized trying to do it by hand with a chisel was just self torture, so I changed tactics and built another tool.

Nothing original here, I’ve seen it done multiple times, but it works great. I put an edge on an old beat up 1/4″ chisel, eyeballed the angle and drilled 1/4″ hole through a scrap bit of 2×4. Then pushed the chisel through the 2×4 whilst twisting it to cut a chisel-sized hole, and I had a working router chisel.

This works surprisingly well. Tap the back of the chisel with a light hammer to advance the chisel, tap the back of the block to move it backwards. Would have worked better if I’d chosen a flat bit of 2/4 (this piece has a pretty good warp in it), but it was good enough to get a consistent depth recess routed.

It worked so well I’ve added making a decent one to my ridiculously long to-do list. I may actually get round-tuit some day. 🙂

Wheel Buttons For A G25

I’ve never been completely satisfied with my home brew button box. There’s nothing wrong with it, it works just fine, but hitting the wrong button or running off the road while looking for the right one was getting old, so I started designing a mod for my G25 (yeah, I know, ancient… mine is fourteen years old) to add buttons right at my fingertips.

Wheel hub unmodified close-up

First thing to figure out is how to attach a plate to the wheel hub. There isn’t a heck of a lot of room and wanting to keep the original buttons, I had to accommodate them in the design.

Modified wheel adapter template

Digging around on the web I located a template for a wheel adapter that allowed standard automotive steering wheels to be bolted onto a G25. I figured out what I wanted the maximum diameter to be and added that circle to the template. Then added a properly scaled photo of the actual wheel to another layer, added a center line for the buttons, and laid out the locations for nine push buttons and a rotary encoder.

Then it was off to the shop, where I grabbed a scrap of sheet plastic, cut off a square on the band saw, and glued on a printed copy of the template.

Drilling holes for the switches

I used a step drill for all the holes, then checked the fit of the hub cover.

Center cover plate fits

Then things got a bit ugly.

Routing a channel for existing switches

I needed space for the original switches and their wire covers. This would have looked a lot better if I’d taken the time to do a layout and cut it on the band saw, but I just threw a carbide routing bit into the Dremel and went at it. It’s all hidden behind the wheel where you have to look hard to see it’s a hack job. Good enough for me.

Sanding to the line

It was then rough cut on the band saw and sanded to the line I’d scribed with a compass earlier.

First test fit

Threw a couple switches on it and did a full test fit. Good enough to go to paint.

Prepped for paint

All sanded and ready to paint. No, it’s not perfectly round, but it’s close enough for me. And yes, that is a sloppy fill job where I’d gone one step too far on the bit on the first hole I drilled.

First coat

Applied light coats and managed to avoid any horrific runs in the paint. I went light enough you can still see some light through the first coat of flat black paint. The red is the light reflecting from my red t-shirt.

Interlude…

Right in the middle of the build, I was reminded why I’m doing this. Two laps from the end of a league race at Barber Motorsports Park, having chased down second place, running less than a second behind, I reached over to the button box to remove a tear-off so I could see, and hit the pit speed limiter instead. Took me a couple seconds to figure out what I’d done, and toggle it back off, but by then 2nd was long gone. Yeah, I’m really looking forward to having the controls I need during a race right under my fingertips instead of on the box on my left.

…back to the build.

Painted with buttons

And it’s painted, with all the buttons and encoder installed. Looks a bit weird in front of the wheel, but I wanted to get an idea how it was going to look when completed.

Time to start writing the code. Not all that much to it really, as three libraries do all the heavy lifting. I used Mark Stanley’s Keypad library, Paul Stoffregen’s Encoder library, and the Joystick library by Matthew Heironimus. The project is available on my github.

I built this using PlatformIO IDE running on top of Microsoft’s Visual Studio. If you do any Arduino development using the Arduino IDE you really should check out PlatformIO – it is so much better.

#include <Arduino.h>
#include <Keypad.h>
#include <Encoder.h>
#include "Joystick.h"

//#define DEBUG
//#define TESTING

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, 
  JOYSTICK_TYPE_GAMEPAD, 12, 0,
  false, false, false, false, false, false,
  false, false, false, false, false);
//Joystick_ Joystick();

const byte ROWS = 3; //four rows
const byte COLS = 3; //three columns
char keys[ROWS][COLS] = {
// original key map
//  {0,1,2},
//  {3,4,5},
//  {6,7,8}
// remapped to put keys in order I preferred
  {4,7,1},
  {5,6,2},
  {3,8,0}
};
byte rowPins[ROWS] = {4, 3, 2}; //connect to the row pinouts of the kpd
byte colPins[COLS] = {7, 6, 5}; //connect to the column pinouts of the kpd

Keypad kpd = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

Encoder myEnc(0, 1);
// encoder button & directions
const int encoderButton = 9;
const int counterClockwise = 10;
const int clockwise = 11;

const byte encSwitch = 9;
int lastButtonState = 0;
int buttonState = 0;
int debounceCount = 0;
const int buttonDebounce = 4;

#ifdef DEBUG
String msg;
#endif

long oldPosition  = -999;

void setup() {
    Serial.begin(115200);
    Joystick.begin();
    pinMode(encSwitch, INPUT);
    #ifdef DEBUG
    msg = "";
    #endif
}

void loop() {
    void joystickToggle(int button);

    // Fills kpd.key[ ] array with up-to 10 active keys.
    // Returns true if there are ANY active keys.
    if (kpd.getKeys())
    {
        for (int i=0; i<LIST_MAX; i++)   // Scan the whole key list.
        {
            if ( kpd.key[i].stateChanged )   // Only find keys that have changed state.
            {
                if (kpd.key[i].kstate == PRESSED) {
                    Joystick.pressButton((int) kpd.key[i].kchar);
                    #ifdef DEBUG
                        msg = " PRESSED.";
                    #endif
                } else if (kpd.key[i].kstate == RELEASED) {
                    Joystick.releaseButton((int) kpd.key[i].kchar);
                    #ifdef DEBUG
                        msg = " RELEASED.";
                    #endif
                }
#ifdef DEBUG
                Serial.print("Key ");
                Serial.print((int) kpd.key[i].kchar);
                Serial.println(msg);
#endif
            }
        }
    }

    // read rotary encoder
    long newPosition = myEnc.read();
    const int countDiv = 3;
    if (newPosition != oldPosition) {
        if (newPosition > (oldPosition + countDiv)) {
            joystickToggle(counterClockwise);
            oldPosition = newPosition;
            #ifdef DEBUG
            Serial.println(newPosition);
            #endif
        } else if (newPosition < (oldPosition - countDiv)) {
            joystickToggle(clockwise);
            oldPosition = newPosition;
            #ifdef DEBUG
            Serial.println(newPosition);
            #endif
        }
    }

    // read rotary encoder switch
    buttonState = digitalRead(encSwitch);
    if (buttonState != lastButtonState) {
        if (++debounceCount > buttonDebounce) {
            if (buttonState == 0) {
                Joystick.pressButton(encoderButton);
                Serial.println(" PRESSED.");
            } else {
                Joystick.releaseButton(encoderButton);
                Serial.println(" RELEASED.");
            }
            lastButtonState = buttonState;
            debounceCount = 0;
        }
    }
}  // End loop

#ifdef TESTING
const int delayTime = 200;      // slow it down so I can see it onscreen
#else
const int delayTime = 12;
#endif
void joystickToggle(int button) {
    Joystick.pressButton(button);       // button pressed
    delay(delayTime);
    Joystick.releaseButton(button);       // button released
}

Wired it up on my desk for testing.

Breadboard setup for testing the code

And, after fixing my stupid mistakes, it works!

And it’s working

Now the “fun” part – stuffing this all into the G25 wheel base. For some reason, the folks that designed the G25 did not put “make it easy to add additional hardware” very high on their list.

Dissecting the wheel base

Buried way down under all this is the opening through which the wires have to be run.

Wires run through hub (the first time)

Note the heat shrink I used for wire management the first time around. This will come back to haunt me.

Dupont connectors used to allow wheel to be removed

I used 22 AWG stranded wire where it was exposed, but had to use 30 AWG wire wrapping wire to allow so many wires to fit through the opening in the base.

Arduino and connector for old shifter

The original G25 shifter stopped working and was replaced a couple of years ago, so the location of the DB9 for the shifter seems like an excellent place to install the proto board mounted Arduino (used a generic HiLetGo branded Pro Micro – $5 each in lots of three on Amazon – even cheaper if you’re willing to order from China and wait).

Removing old DB9 connector

Pulled the old connector and cable.

Modifying DB9 mount

Trimmed away some unneeded plastic to make some room.

Arduino fitted

And the Arduino is fitted up and ready to be wired.

Wire wrapping Arduino

Then I wire wrapped all the switches to the Arduino and completely reassembled the wheel before starting final testing. Did you notice I said completely reassembled before testing? Three of the buttons registered as two simultaneous button presses on the computer. Pull out the DVM and sure enough, two of the six wires used for the keypad scanning are shorted together.

Completely disassemble the wheel again. This is not a five minute job… closer to half an hour, or more. Remember that red arrow? Seems I got a bit carried away shrinking that heat shrink tubing and melted the insulation on the wire wrapping wire. I forgot how much easier it was to melt than normal wire insulation.

Together again

Pulled all the wire wrapping wire out, de-wired the Arduino, and started all over again, from crimping on the Dupont connector pins all the way to rewiring the Arduino. Decided not to melt the insulation again, using small cable ties this time instead of the shrink wrap.

Used cable ties the second time around

Yes, there is quite a bit of extra wirewrap wire. There are enough unused inputs on this Arduino to connect the pedals and hand brake, which are currently hooked up to another Arduino hanging off the side of the rig. Wirewarp wire is rather brittle and tends to break when you unwind it, so you need start with a freshly stripped bit every time. I’ve left enough here to allow the wires to be moved several times. The last thing I want to do is rip the wheel completely apart to run new wire.

The ugly, but functional, pedals and handbrake controller.

And… everything works!

Completed G25 Upgrade

Hot Raspberry Pi

I have a Raspberry Pi 3 that takes care of some IOT odds and ends around here. The other day I noticed it was running a bit warmer than I’d like, so I threw together this quick hack using a 20 mm fan I’d parted out of an old junk laptop a while ago, a 2N2222 transistor, 1N4001 diode, and 1K resistor.

The circuit required looks like this (click on image to see instructables.com article).

Circuit diagram from instructables.com article

First, I bread-boarded a circuit to drive the fan via one of the Pi’s PWM outputs. The Raspberry Pi PWM pins can’t source enough current to drive the fan (and the 3.3V output wouldn’t drive the 5V fan to full speed anyway), so I used a 2N2222 to control the 5V supply. A 10K resistor between the PWM output and the base, emitter to ground, and the collector connected to the fan. The other fan lead goes to the 5V supply. You need a diode across the motor so inductive voltage spikes don’t damage the transistor.

With that tested I fit the parts on a bit of protoboard.

Layout parts

Soldered parts, power, ground, PWM input, fan leads to the board.

Then hacked a hole into the plastic case, mounted the fan, and installed standoffs to hold the board.

Next the protoboard was mounted to the standoffs.

Snapped on the cover and we’re ready to roll.

The code to drive it is fairly trivial. While the fan will run at 30%, it won’t start, so in the function that controls the fan I give it a short kick at 100% to get it going if it was previously off.

/*
Temperature controlled Raspberry Pi cpu fan via hardware PWM
Wiring diagram available here:
https://www.instructables.com/id/PWM-Regulated-Fan-Based-on-CPU-Temperature-for-Ras/
*/

#include <wiringPi.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <string.h>

const int PWM_pin = 1;   /* GPIO 1 as per WiringPi, GPIO18 as per BCM */
bool debug = false;

int main (int argc, char *argv[])
{
	FILE *tempFile;
	int temp;
	double T;
	void setFan(int);

	if ((argc == 2) && (strcmp("-d", argv[1]) == 0))
		debug = true;

	// can't do anything if setup fails
	if (wiringPiSetup () == -1) {
		fprintf(stderr, "wiringPi setup failed");
		exit (1) ;
	}

	// setup output
	pinMode (PWM_pin, PWM_OUTPUT) ; /* set PWM pin as output */

	while (1) {
		// get temperature
		tempFile = fopen("/sys/class/thermal/thermal_zone0/temp", "r");
		if (tempFile == NULL) {
			fprintf(stderr, "File open failed\n");
			exit(1);
		}

		fscanf(tempFile, "%lf", &T);
		T /= 1000;
		if (debug)
			printf("CPU temperature is %6.3f C\n", T);
		fclose(tempFile);
		temp = (int) T;		// don't care about details, so just truncate it

		setFan(temp);
		sleep(10);
	}

	exit(0);
}

// set fan speed based on CPU temperature
void setFan(int temp) {
	long map(long, long, long, long, long);
	int speed;
	static int last_speed = 0;

	if (temp < 50)			// under 50C don't run fan
		speed = 0;
	else if (temp > 80)		// anything over 80 run 100%
		speed = 100;
	else {					// vary speed as temp increases
		speed = map(temp, 50, 80, 30, 100);
	}

	if (speed != last_speed) {
		if (debug)
			printf("Setting fan to %d\n", speed);

		if (last_speed == 0) {
			pwmWrite(PWM_pin, 100);		// give it a kick start
			delay(100);					// let fan start
		}
		pwmWrite(PWM_pin, speed);
		last_speed = speed;
	}
}

long map(long x, long in_min, long in_max, long out_min, long out_max)
{
	return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

I installed the compiled program in /usr/local/bin and added a line in rc.local to run it when the Raspberry Pi boots. Where I’ve set the temperature threshold, the fan kicks on occasionally while the Pi is idle. As soon as any sort of load is added, the fan starts almost immediately and speeds until the temperature levels off. The fan provides more air than is actually needed to keep the Pi cool, as even under full load the fan doesn’t come up to full speed.

Lee Classic Single Stage Brass Kicker

When I upgraded to a Lee Classic Turret Press a couple of years ago, I picked up a universal decapping die and turned my old Lee Classic Press into my dedicated decapping setup.

The prototype was bits of wood clamped onto the primer arm. A bit of hot glue here and there to get the geometry right and I had a working brass kicker. The only issue with that one was that the screw holding it all together kept working it’s way loose. My solution was a piece of 1/4″ aluminum plate cut into the correct shape. I did have some issues with the center of gravity being higher, causing it to lift up off the pivot pin, but that was easily solved with a short ramp hot glued onto the wooden base plate, moving the force rotating it a bit further away from the pivot point.

I made this mod to speed up the decapping process early in 2018, shot a quick video about it, but never got around to mentioning it here. willwood487 just posted a video about a 3D printed implementation of the same idea, so I figured I’d mention them both here.

Not a heck of a lot more to say. Here’s my version.

willwood487’s looks a lot classier than mine (as I’m not particularly skilled at milling metal), but they both work. Here’s his.

33 Button Box with Rotary Encoders

Controlling your car in a sim using the keyboard isn’t ideal. Too many keys you don’t need placed to closely together to operate by feel. Taking your eyes off the road long enough to find and press the right key often results in unwanted off-road excursions. A button box with which control all the things that need changing during a race is a much better solution.

So, I’ve been looking at amstudio’s sim racing button box for over a year, but just got a’round tuit. Didn’t buy a box. Front panel is leftover acrylic sheet I had lying around the shop. This thing ain’t gonna be pretty, but it’ll work.

Probably should have painted it before installing the switches.

This is actually a complete rewire after I’d totally botched it the first time. I messed up the grid, and after trying to fix it three or four times, just ripped it all out, figured out the correct layout on paper, and did it right.

I suppose I could have painted the panel but I plan on using more than one layout and decided to just slap a overlays on it.

Sliced up a 2×4 to make the sides of the box.

Continuing the “use junk already in the shop” theme, I re-sawed a piece of 2×4 to create the sides of the box. Planed them smooth, cut bridle joints, then assembled and glued the frame.

Smooth and planed to uniform thickness.
The glue up.
Turns out the front panel was slightly out of square. Shooting board took care of that.

I did have to tweak the panel a tiny bit as the frame was perfectly square, but the panel was off by a few thou. Then added some blocking to support and mount the panel.

Glued in some corner blocks for mounting the panel.
Added blocks in the middle to stop the panel flexing, then cut and glued in a piece of scrap 1/4″ plywood for the back.
Might not be pretty, but it works just fine.

I have a few small bits of 1/4″ plywood in the scrap bin, so that’s what I used for the back and part of the stand. The stand uprights were made from the pine cutoffs from the foot stool I made for my wife a while ago.

Screwed together a bracket and screwed into the “cockpit”.
And it works just fine.

And there you go. Installed and working. The overlays seem to stay on just fine by laying them over the switches. These are card stock. When I finalize the layouts I may go with something heavier and some Velcro to keep it in place.

Used the Illustrator panel layout to create a template for holes for all the switches.

It’s a bit of a PITA to cut out the overlays with an X-Acto knife, but I only use a couple different switch layouts. I’ll eventually print up a nice one once I’m happy with the switch layout and retire the hand written in pencil layouts I’m currently using.

Check out amstudio’s video for a the software for the Arduino. All I’ve changed are the limits to include one more switch.

Sim Racing E-Brake Rev. 1.0

I’ve been driving Dirt 2.0 a lot lately and decided using a button for the hand brake wasn’t ideal. It was time for an analog hand brake. Club Sport makes a really nice one for $120 plus shipping and handling.

Not. Gonna. Happen. I’m too cheap to spend that for something that simple. Time to roll my own.

A nice, home-made kludge would look right at home installed on my crude racing rig… except there’s a mouse platform in the way.

But that’s not really much of an obstacle when everything is made of wood. Ready to start prototyping.

So, where exactly do I want it? Nothing I tried was quite right because I was stuck placing it just a little too far to the right, but this was as good as I could get.

It needs to pivot and return when released. Check.

It needs to connect to a linear potentiometer. Added high tech coat hanger push rod. Check.

It needs a stop so I don’t break the potentiometer by pulling too far. Check.

With the mechanics all working, I wired the pot to an Arduino Pro Micro installed in a solderless prototype board – this was super complicated, all three wires. 5V, ground, and A0 input. Loaded the sketch amstudio used for his handbrake (YouTube https://youtu.be/kv0FTpRLFMY) off github (https://github.com/AM-STUDIO/Analog-E-Brake), et voilà, I have a working hand brake.

Total cost? $12.50. The 10K slide pot was $5.50 and the Arduino Pro Micro $7. Everything else was scrap that was lying around the shop. Well, I had spare Arduinos lying around from another project – they’re so cheap it doesn’t make sense not to order several at a time, so the only thing ordered specifically for this build was the $5.50 10K pot.

Eventually, I’ll build one that’s an enclosed unit, but for now this will do. It could also use a stronger spring and better stops, but it works great.

So, It’s Done For Now

At least as done as I need it to be.

Not pretty, just functional

Used a piece of scrap 2×4, resawn to a little over 3/8″.

Made some slabs from an old 2×4

Planed cut smooth after each slice.

Then smoothed the rough side of each slab. My bench hook was a little too tall, so I had to shim them up with a bit of 1/4″ plywood so the plane would clear.


I held the LHSWBBT next to one of the slabs at what felt like a good angle, and ran a pencil line. Then stuck the two sides together with some double sided tape and cut on the band saw.

Cut sides on band saw and planed smooth

I needed rabbets and dados for the top and PCB. Started out cutting free-hand with a utility knife (they’re only about 1/8″ deep), but realized it would be a lot easier with the Dremel plus router base and a straight edge.

Pretty obvious which one was free-hand and which was done with the straight edge and Dremel

There is no pretty joinery here, or even any attempt at it. The side which supports the hand is one of the slabs laid flat and planed to the same angle as the sides. The dados hold the PCB in place and everything is held together with three screws on each side.

Almost there. Sides still need to be trimmed to length

The bottom has been left open (easy to rewire/add things if I choose). Undo the four top screws and the cover lifts off. Nothing fancy, but this thing is fully functional.

Stuff that works…

How’s It Hooked Up?

The software is here. Just a simple little hack. The final version is almost the same as what’s posted. All that’s changed is some extraneous junk has been removed, and the thumb button now acts like the left-control key on the keyboard.

And here’s a fancy hand-drawn schematic.

Here you go

The Return of the Left Hand Scroll Wheel Button Box Thingie

So, several months later… (what am I talking about?)

I finally get ’round to doing more with the LHSWBBT. Going ultra-cheap isn’t the fastest way to get something done. Do you spend $10-$15 on a part or order ten of them for $1.50 and wait 4-6 weeks for it to show up? If you’re going ultra-cheap, you take option number two.

When combined with mistakes, like not noticing the the perf-board you ordered has smaller than normal through holes, and you can’t install the PC mount switches, it takes even longer.

Putting It Together

Mounted the switches and encoder

First step, mount the switches and encoder to the PC proto-board. Layout was detailed and scientific – I set my hand on a piece of paper and marked where my fingers fell. Drilled the hole for the encoder and soldered the switches to the board.

We’re committed now

I had a large sheet of acrylic in the shop suitable for the project, from which top panel was cut on the band saw. This leaves somewhat nasty edges on the acrylic, but a Stanley No.5 bench plane set for a thin cut does an excellent job of cleaning up, straightening and squaring up the edges.

I located the mounting holes for the acrylic by taping the PCB to it and drilling through the existing holes in the PCB. Then I installed extra long standoffs in PCB so the acrylic could be positioned over the mounted switches, and marked the rough center of each with a Sharpie.

Routing the holes.

Knowing I wouldn’t get the alignment close enough if I drilled final sized holes, I drilled starter holes and used a Dremel with a router base clamped upside down in the vise to open up the holes a bit at a time until they fit.

Close enough

Hooking Everything Up

I went for crude but simple, and easily modifiable. I installed Berg pins for the connections to every component and wire-wrapped the whole thing. Fast and easy, and it makes it super simple for the LHSWBBT to evolve over time.

All parts installed.

It may be ugly, but it’s fully functional.

Everything connected to Berg pins and wire-wrapped.

And yes, it does work. Still needs some updates to the software. I’ve given up on getting the Arduino library to work with more than three mouse buttons, and have decided that the thumb button would be more useful as a modifier key such as Control. I haven’t updated the Button class to be able to transmit keyboard events as yet, so the thumb button isn’t doing anything.

Fully functional, sans case.

As you have probably noticed, there isn’t any case for this thing yet. It’s just sitting on stand-offs. I plan on wrapping it in a wooden frame whenever I get another ’round-tuit.