As of 2020 I’ve been messing about with solid body electric guitars since 1986. I’ve built many instruments for myself and others over those 34 years. Sometimes ‘from scratch’ with lumps of wood and my trusty router and other times assembled from parts ordered from places like Warmoth. I’ve turned my hand to pretty much every aspect of this hobby, but never the actual pickups. And there isn’t another component on an electric guitar that influences the tone more than the pickups. So it is time to jump on that bandwagon and make some.
There are a few choices when it comes to pickup coil winding machines. Schatten and Mojotone spring to mind, but they are in the $500-600 range, which is quite expensive. When it comes down to it, all you need is a spinning thing to attach a bobbin to, and a way to stop it when the desired winding count is achieved. My grown-up job is writing code, and I’ve played around with an Arduino a bit in the past. Now is the time to combine all these things and build a machine.
Working Prototype
First some photos. The details are underneath.

This is for the bobbin to mount to.
I used 6mm MDF as it is very easy to work with, cheap and strong enough for what I’m doing. As I was building this prototype around my breadboard I went with simple L shaped brackets and M4 screws and nuts to hold it together. The small hex head screws you see above are M3 size. MDF isn’t great to drill in that if you don’t have really sharp drill bits (which I don’t) you can get tear-out.

I used a 10mm round neodymium magnet glued to the back of the bobbin plate. I had to make sure I glued it with the correct pole facing the Hall sensor. It would not work otherwise.

As my breadboards were stuck to a reasonably large bit of plywood I was able to secure most components to it without worrying too much about space. The breadboard jumper cables were generally long enough to reach where I needed them to.

From left to right these are the button functions:
START/STOP – this will start and stop the motor. It will stop automatically when the desired winding count is reached.
MODE – This switches between RUN mode and SPEC mode.
SELECT – When in select mode this button cycles between the 3 settings: desired count, direction and auto-stop.
UP/DOWN – When editing a specific spec value these two with either increment/decrement the desired count by 100, flip between clockwise/anticlockwise and toggle auto stop. Also, while in RUN mode, the DOWN button resets the counter.
NOTE: I started off using cheap switches I bought off eBay for something like $5 for 10 switches. They were awful and mostly broken. It then crossed my mind that Ive used Switchcraft jacks and sockets for almost 40 years, so maybe they make switches? You know…Switch….Craft? And waddaya know? They make very good switches for about $4 a piece, and the work extremely well.

This is the very start of my first winding attempt, so this was very exciting. I had the spool of wire on the floor sitting on its side so that the wire could spool off freely. I was going to be holding the wire between my thumb and first finger to maintain tension. How much tension? I had no idea. So my fingers would be just out of shot a few centimeters below the limiters and I would be moving from side to side to spread the wire across the bobbin. Before hitting the start button I moved the wire towards the middle of the bobbin. At this point I had no idea how it would feel once the winding started. Would the wire snap? Would it spin around in a wild fashion and I’d have a ball of copper wool on my hands?

It turns out this is easier than I expected. I find myself staring along the wire as it goes on to see the edges of the coil as it builds up. You can sense a shape forming as naturally doing this by hand isn’t really all that accurate. So that led to one varying the speed at which I moved left and right to keep things even. A genuine coil winding engineer would call this a failure. Guitarists call it ‘scatter wound’ and have somehow legitimized it through force of will.

And there it is. A finished coil. I have a bit of gaffer tape on the wire just to hold it in place. I had not yet bought the correct paper-based tape, which is much thinner.
Basics of Operation
For this prototype all I wanted to achieve was this: “Spin a bobbin at approximately 1000rpm for a given number of revolutions”
To achieve the machine had to be able to do the following:
- DC motor controlled by pulse-width-modulation (PWM) using Arduino and an L298
- Keep track of revolution count using a Hall sensor.
- Specify motor direction.
- Stop the motor when we achieve the revolution count.
And that’s really it. The following quick video shows the Hall sensor in action, a quick demo of editing the count, and a short run showing the auto-stop mechanism.
Component Details
Item | Source | Comment |
Switchcraft 933 momentary switch | From Mouser | These work flawlessly compared to the cheap crap ones I got from China on eBay |
Greartisan 12 volt DC 1,000 RPM motor | eBay | I’m not impressed by this motor as even though I am using PWM and a motor driver board I cannot get variable speeds. But it works fine for the prototype. |
L7812 voltage regulator | SparkFun | A basic voltage regulator to give me smooth DC from my 18v supply. |
18V AC supply | ? | I had some 18V 3 amp power supplies laying around, so I used one along with a rectifier I made with some diodes. That’s what fed the L7812 regulator. |
HD44780 based LCD | eBay | The usual LCD 16 character, 2 row display with the I2C board attached so I don’t use up too many Arduino pins. |
L298 based motor driver | Amazon | Again, this one connects via I2C, so it’s trivial. |
A3144 hall sensor | Amazon | I liked this board mounted Hall sensor as it had a built in LED, which made aligning the magnet easy. Otherwise it’s a normal non-latching sensor. |
The source code
This is my first Arduino code, so forgive the lack of brevity.
To summarize this code in one sentence: “decide on what to do with the motor based on internal state controlled by the buttons”.
In RUN mode you get to start and stop the motor, wherein it starts counting revolutions until it gets to the desired value. In SPEC/EDIT mode you use other buttons to cycle through each value and change them with the UP/DOWN buttons.
#include <Wire.h> // Library for I2C communication #include <LiquidCrystal_I2C.h> // Library for LCD // Wiring: SDA pin is connected to A4 and SCL pin to A5. // Connect to LCD via I2C, default address 0x27 (A0-A2 not jumpered) LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2); // for 16x2 LCD. const int motor1pin1 = 11; const int motor1pin2 = 12; const int startStopButtonPin = 4; const int modeButtonPin = 5; const int selectButtonPin = 6; const int upButtonPin = 7; const int downButtonPin = 8; const int enaPin = 9; const byte interruptPin = 2; const int SPEC_SUMMARY = 0; const int WINDINGS_EDIT = 1; const int DIRECTION_EDIT = 2; const int AUTO_STOP_EDIT = 3; const int RUN_MODE = 42; const int SPEC_MODE = 84; const int CLOCKWISE = 1; const int ANTI_CLOCKWISE = 2; boolean runMode = true; boolean shouldBeRunning = false; boolean shouldRedraw = true; boolean clockwise = true; boolean autoStop = true; unsigned long desiredWindingCount = 4500; volatile unsigned long totalRevolutions = 0; int currentSpecSelection = 0; struct ButtonModeStruct { int pin; int buttonState; int lastButtonState = LOW; // the following variables are unsigned longs because the time, measured in // milliseconds, will quickly become a bigger number than can be stored in an int. unsigned long lastDebounceTime = 0; // the last time the output pin was toggled unsigned long debounceDelay = 75; // the debounce time; increase if the output flickers }; typedef struct ButtonModeStruct ButtonMode; ButtonMode startStopButton; ButtonMode modeButton; ButtonMode selectButton; ButtonMode upButton; ButtonMode downButton; void setup() { lcd.init(); lcd.backlight(); Serial.begin(9600); // These two govern direction pinMode(motor1pin1, OUTPUT); pinMode(motor1pin2, OUTPUT); pinMode(enaPin, OUTPUT); //PWM for averaged voltage, therefore motor speed pinMode(startStopButtonPin, INPUT); startStopButton.pin = startStopButtonPin; modeButton.pin = modeButtonPin; selectButton.pin = selectButtonPin; upButton.pin = upButtonPin; downButton.pin = downButtonPin; attachInterrupt(digitalPinToInterrupt(interruptPin), count, RISING); } void loop() { checkModeButton(&modeButton); if (runMode) { currentSpecSelection = 0; checkStartStopButton(); renderRevCount(); if (shouldBeRunning) { startMotor(); } else { stopMotor(); } if (shouldRedraw) { renderRunModeDisplay(); shouldRedraw = false; } // Use this for reset when in run mode checkDownButton(&downButton); } else { stopMotor(); totalRevolutions = 0; checkSelectButton(&selectButton); checkUpButton(&upButton); checkDownButton(&downButton); if (shouldRedraw) { renderSpecModeDisplay(); shouldRedraw = false; } } delay(1); } void renderRevCount() { lcd.setCursor(9, 1); lcd.print("#:"); lcd.print(totalRevolutions); lcd.print(" "); } void count() { totalRevolutions++; if (autoStop) { if (totalRevolutions > desiredWindingCount) { shouldBeRunning = false; totalRevolutions = 0; } } } void renderAutoStop() { //autostop lcd.setCursor(10, 0); lcd.print("Auto:"); if (autoStop) { lcd.print("Y"); } else { lcd.print("N"); } } void renderCount() { //count lcd.setCursor(0, 1); lcd.print("No.:"); lcd.setCursor(4, 1); lcd.print(" "); lcd.setCursor(4, 1); lcd.print(desiredWindingCount); } void renderDirection() { //direction lcd.setCursor(10, 1); lcd.print("Dir:"); if (clockwise) { lcd.print("CW"); } else { lcd.print("CC"); } } void renderSpecModeLabel() { lcd.setCursor(0, 0); lcd.print("Mode:SPEC "); } void renderEditModeLabel() { lcd.setCursor(0, 0); lcd.print("Mode:EDIT "); } void renderSpecModeDisplay() { if (currentSpecSelection == SPEC_SUMMARY ) { lcd.clear(); renderSpecModeLabel(); renderAutoStop(); renderCount(); renderDirection(); } if (currentSpecSelection == WINDINGS_EDIT ) { lcd.clear(); renderEditModeLabel(); renderCount(); } if (currentSpecSelection == DIRECTION_EDIT ) { lcd.clear(); renderEditModeLabel(); renderDirection(); } if (currentSpecSelection == AUTO_STOP_EDIT ) { lcd.clear(); renderEditModeLabel(); renderAutoStop(); } } void renderRunModeDisplay() { lcd.setCursor(0, 0); lcd.print("Mode:RUN "); renderCount(); } void checkSelectButton(ButtonMode * button) { // read the state of the switch into a local variable: int reading = digitalRead(button->pin); // If the switch changed, due to noise or pressing: if (reading != button->lastButtonState) { // reset the debouncing timer button->lastDebounceTime = millis(); } if ((millis() - button->lastDebounceTime) > button->debounceDelay) { // whatever the reading is at, it's been there for longer than the debounce // delay, so take it as the actual current state: // if the button state has changed: if (reading != button->buttonState) { button->buttonState = reading; // only toggle the LED if the new button state is HIGH if (button->buttonState == HIGH) { if (currentSpecSelection == 4) { currentSpecSelection = 0; } else { currentSpecSelection = currentSpecSelection + 1; } shouldRedraw = true; } } } // save the reading. Next time through the loop, it'll be the lastStartStopButtonState: button->lastButtonState = reading; } void handleUpButton() { if (currentSpecSelection == WINDINGS_EDIT) { desiredWindingCount = desiredWindingCount + 100; if (desiredWindingCount < 100) { desiredWindingCount = 100; } } if (currentSpecSelection == DIRECTION_EDIT) { clockwise = true; } if (currentSpecSelection == AUTO_STOP_EDIT) { autoStop = true; } } void handleDownButton() { if (currentSpecSelection == WINDINGS_EDIT) { desiredWindingCount = desiredWindingCount - 100; if (desiredWindingCount < 100) { desiredWindingCount = 100; } } if (currentSpecSelection == DIRECTION_EDIT) { clockwise = false; } if (currentSpecSelection == AUTO_STOP_EDIT) { autoStop = false; } if(runMode) { totalRevolutions = 0; } } void checkDownButton(ButtonMode * button) { // read the state of the switch into a local variable: int reading = digitalRead(button->pin); // If the switch changed, due to noise or pressing: if (reading != button->lastButtonState) { // reset the debouncing timer button->lastDebounceTime = millis(); } if ((millis() - button->lastDebounceTime) > button->debounceDelay) { // whatever the reading is at, it's been there for longer than the debounce // delay, so take it as the actual current state: // if the button state has changed: if (reading != button->buttonState) { button->buttonState = reading; // only toggle the LED if the new button state is HIGH if (button->buttonState == HIGH) { handleDownButton(); shouldRedraw = true; } } } // save the reading. Next time through the loop, it'll be the lastStartStopButtonState: button->lastButtonState = reading; } void checkUpButton(ButtonMode * button) { // read the state of the switch into a local variable: int reading = digitalRead(button->pin); // If the switch changed, due to noise or pressing: if (reading != button->lastButtonState) { // reset the debouncing timer button->lastDebounceTime = millis(); } if ((millis() - button->lastDebounceTime) > button->debounceDelay) { // whatever the reading is at, it's been there for longer than the debounce // delay, so take it as the actual current state: // if the button state has changed: if (reading != button->buttonState) { button->buttonState = reading; // only toggle the LED if the new button state is HIGH if (button->buttonState == HIGH) { handleUpButton(); shouldRedraw = true; } } } // save the reading. Next time through the loop, it'll be the lastStartStopButtonState: button->lastButtonState = reading; } void checkModeButton(ButtonMode * button) { // read the state of the switch into a local variable: int reading = digitalRead(button->pin); // If the switch changed, due to noise or pressing: if (reading != button->lastButtonState) { // reset the debouncing timer button->lastDebounceTime = millis(); } if ((millis() - button->lastDebounceTime) > button->debounceDelay) { // whatever the reading is at, it's been there for longer than the debounce // delay, so take it as the actual current state: // if the button state has changed: if (reading != button->buttonState) { button->buttonState = reading; // only toggle the LED if the new button state is HIGH if (button->buttonState == HIGH) { runMode = !runMode; shouldRedraw = true; if (runMode) { Serial.println("run"); } else { Serial.println("spec"); } } } } // save the reading. Next time through the loop, it'll be the lastStartStopButtonState: button->lastButtonState = reading; } void checkStartStopButton() { // read the state of the switch into a local variable: int reading = digitalRead(startStopButton.pin); // If the switch changed, due to noise or pressing: if (reading != startStopButton.lastButtonState) { // reset the debouncing timer startStopButton.lastDebounceTime = millis(); } if ((millis() - startStopButton.lastDebounceTime) > startStopButton.debounceDelay) { // whatever the reading is at, it's been there for longer than the debounce // delay, so take it as the actual current state: // if the button state has changed: if (reading != startStopButton.buttonState) { startStopButton.buttonState = reading; // only toggle the LED if the new button state is HIGH if (startStopButton.buttonState == HIGH) { shouldBeRunning = !shouldBeRunning; } } } // save the reading. Next time through the loop, it'll be the lastStartStopButtonState: startStopButton.lastButtonState = reading; } void startMotor() { if (clockwise) { digitalWrite(motor1pin1, HIGH); digitalWrite(motor1pin2, LOW); } else { digitalWrite(motor1pin1, LOW); digitalWrite(motor1pin2, HIGH); } analogWrite(enaPin, 255); } void stopMotor() { analogWrite(enaPin, 0); }
What’s Next?
I’d like to improve a few things to fully automate the winding of a pickup coil:
- Servo motor with rack and pinion to guide the wire across the bobbin for even spread.
- Tension system to feed the wire properly.
- Something to house the wire spool. It tends to flap about wildly as it spools off the reel. Britain built an Empire off solving this problem so perhaps a 200 yr old book will have answers? I was thinking a smooth plastic tube would do the trick.
- A decent housing for the entire machine. i.e. take it beyond a prototype.
- 5V power supply. Currently it is powered off my Mac’s USB bus for 5V.
- The motor I have is crap. It will do 1000rpm at 12V, but it’s only getting 10V via the drive board so it’s slower than that. I need a motor that will do as much as 1500rpm but also speeds lower than that so I can do a slow start.
Finished pickups




December 2020 – Simon Allaway