Final Project¶
Slide¶
Presentation¶
The Idea & Result¶
Now that I’ve presented the project I can explain the idea I settled on. The machine takes a stack of cards and dispenses one at a time. The main “brain” of the machine is a custom board using the Seeed Studio Xiao RP2040 and a dual DRV8251A H-Bridge stepper driver. The machine uses two endstops, one at the top for detecting card positions and one at the bottom for zeroing. The entire thing is operated using a single button with instructions and readouts over serial to the connected computer. The whole thing is powered over 5V from USB. The USB port needs to handle at least 2A of current so not all on-board USB ports work but using an external USB hub with separate power is safe. Currently running the machine on lower-end machines can result in USB brownouts.
“A machine that feeds one card at a time into a scanning area, where a phone or computer can scan the card saving hours of manual handiwork. The device uses a lead-screw driven platform to lift the card stack and a servo-actuated arm to swipe each card into the scanning area. Built around a custom PCB with a Seeed Studio Xiao RP2040 and dual DRV8251A H-Bridge drivers for stepper control, the machine runs entirely on 5V USB power. Operated via a single button, with status and instrucdtions output over USB serial to a connected computer. It uses two endstops as input for detecting card position and error handling.”
Final Project Requirements¶
Below I’ll go over the checklist from Nueval answering each question.
What does it do?¶
The Card-A-Pult is a machine I designed to help me catalog and keep track of my collection of Magic the Gathering trading cards. It takes a stack of cards and feeds out single cards into a scanning area where I can mount a phone or webcam and use third party software to catalog the cards. This saves me hours of work manually scanning them in. The machine can detect the thickness of the card stack and figure out when it’s done dispensing the stack. It also has error handling for incorrect card feeding and it’s controller entirely using a single button with instructions and readout over USB serial. Once started it automatically feeds the stack from the lift to the scan chute.
What did you design?¶
A surprisingly reliable card dispenser. The mechanical parts consist of the servo swiper arm and lift platform. The machine can detect the thickness of the card stack and dispense single cards. The PCB design includes a custom Dual DRV8251A H-Bridge stepper driver with current limit and ground-fill cooling. One of the main things I focused on whilst designing it was making sure it was as adjustable as possible during assembly saving me time compared to re-printing each component to adjust for tolerance. The Servo arm is adjustable in length and the center of rotation is adjustable on X Y and Z.
Who’s done what beforehand?¶
I found One FabAcademy Final Project from Diego Santa Cruz. It looks rather incomplete but amounts to a webcam mounted to a laser cut frame pointed at a card and using OCR to pull the card’s title. That’s fine enough if all you care about is the mechanics of the game but since the same card can have multiple printings of wildly different value it doesn’t help me at all. His method narrows the 89.029 cards down to 29.865.
I also found this playlist from Jack Baumgartel where he covers his progress making and designing a Magic the Gathering scanning/sorting machine. This has proved an excellent resource for mechanical principles and inspiration.
What sources did you use?¶
The aforementioned playlist from Jack Baumgartel was a huge resource in figuring out the mechanics for the swiper arm. I also took a lot of inspiration from Neil’s DRV8251A board for the PCB design.
What materials and components were used?¶
Here’s the BOM breakdown:
Material | Amount | Cost | Link |
---|---|---|---|
Addnorth X-PLA | 959.47g | 4990 Icelandic Króna per 750gr. | Link |
Nema 17 Stepper. 42x42x34 | 1x | $ 29.99 for a pack of 5 | Link |
150mm T8 TR8x8 Lead Screw & Nut | 1x | $ 7.89 | Link |
10x1360mm steel rod | 2x | N/A | None |
2.5mm Lasercut aluminum plate | 1x | N/A | None |
3mm Clear Plexyglass 30x60cm | 1x | 3000 Icelandic Króna | None |
The PCB components were taken from the Fab Lab Inventory so I don’t have exact unit pricing but I’ll list them here below:
Partname | Amount |
---|---|
FR4 copper plate | 1x |
Seeed Studio Xiao RP2040 | 1x |
Texas Instruments DRV8251A | 2x |
1206 1K Resistor | 2x |
1206 6.81K resistor | 2x |
1206 49.9K resistor | 2x |
1206 47.9 uF Capacitor | 2x |
1206 .1 uF Capcaitor | 1x |
01x02 Horizontal SMD Pin Socket | 3x |
4 pin JST connector | 1x |
01x07 SMD Socket header | 2x |
Big Button | 1x |
Assorted cables | too many |
Where did they come from?¶
The stepper, Lead screw and rods were scrapped from an old milling machine but the electronic components and servo came from the lab. The filament was bought from a supplier here in Iceland: 3DVerk.is
How much did they cost?¶
See BOM above.
What parts and systems were made?¶
Here’s a screenshot of the whole project in CAD:
All the red parts, The electronics, lift platform and PCB Enclosure cover were designed/fabricated.
Here are the Schematic and PCB:
Design and production files.¶
Here are the two main design files:
Filename | Program | Description | Type | Link |
---|---|---|---|---|
Card-A-Pult-Complete | Autodesk Fusion | The entire assembly in one Fusion file | .f3d | Link |
Card-A-Pult-Board | KiCad | Entire KiCad Project | .7z | Link |
Here’s a list of the designed files:
3D Printed parts:
Filename | Description | Type | Link |
---|---|---|---|
Lift | The central part where the platform lifts cards towards the swiper. | .3mf | Link |
PCBEnclosure | The PCB Enclosure | .3mf | Link |
CardChute | The chute cards fall into before scanning | .3mf | Link |
RodMount | Mount for servo arm to rods | .3mf | Link |
ServoArm | Arm from RodMount to ServoClamp | .3mf | Link |
ServoClamp | Holds Servo | .3mf | Link |
SwiperArmUpper | Upper half of swiper arm | .3mf | Link |
SwiperArmLower | Lower half of swiper arm | .3mf | Link |
2D Files:
Filename | Description | Type | Link |
---|---|---|---|
PCBEnclosurePlate | 3MM Plexyglass plate that goes on top of PCB enclosure to hold button | .dxf | Link |
CardLiftPlate | 2.5mm Lasercut Aluminum or 3mm Plexyglass plate, Lifts cards. | .dxf | Link |
The Code:¶
The program is relatively simple. Due to time constraints this was mostly “Vibe Coded” but obviously I understand what it’s doing and have commented it myself. Here is the chatGPT conversation. I’ll briefly go over what the program does but it’s also very clearly commented here below.
When the machine powers up: - Servo moves to home position - Waits for first button press.
On first button press: - Machine moves to zero at the bottom endstop. - Prompts the user to insert the cards. - Waits for second press.
On second press: - Machine starts the “feed loop” - Moves from the bottom position towards the top end stop to detect the thickness of the card stack. - Moves down by a set offset - Swipes card - Moves upwards to the endstop while keeping track of steps travelled - As long as the step count is not less than or equal to the previous step count the loop repeats.
Once the stack height no longer decreases the machine stops.
I’ve broken most of this down into separate functions, As an example here’s the swipe function:
/* perform one swipe:
- attach servo
- move to SERVO_REST
- move to SERVO_HOME + SWIPE_ANGLE
- move to SERVO_HOME - SWIPE_ANGLE
- repeats SWIPE_REPEATS times
- move back to SERVO_REST
- detach servo
*/
void swipe() {
if (!swipeServo.attached()) swipeServo.attach(SERVO_PIN);
swipeServo.write(SERVO_REST); delay(200);
for (uint8_t i = 0; i < SWIPE_REPEATS; ++i) {
swipeServo.write(SERVO_HOME + SWIPE_ANGLE);
delay(SWIPE_DELAY_MS);
swipeServo.write(SERVO_HOME - SWIPE_ANGLE);
delay(SWIPE_DELAY_MS);
}
swipeServo.write(SERVO_REST); delay(200);
swipeServo.detach();
}
Below is the download link for the program and the full code in a dropdown:
Filename | Type | Link |
---|---|---|
Final | .ino | Link |
⌨️ show / hide Final.ino
/* -------------------------------------------------------------
Card Scan Thing - Magnús Pétursson - FabAcademy 2025
-------------------------------------------------------------
– On power-up: servo → SERVO_HOME → detach
– Wait for 1st press: zero at BOTTOM endstop → prompt “Insert cards”
– Wait for 2nd press: start feed loop:
- HOME → TOP endstop (detect stack height), swipe
- move down 4 mm, home → TOP, swipe
- repeat until stack height no longer decreases
- Once stack height no longer decreases
(i.e. no more cards or feed error) the machine stops.
-----------------------------------------------------------------*/
#include <Arduino.h>
#include <Servo.h>
/* --- Pin Definitions --- */
/* Motor Pins:*/
const uint8_t IN1A = 27; // 1A
const uint8_t IN1B = 28; // 1B
const uint8_t IN2A = 6; // 2A
const uint8_t IN2B = 7; // 2B
const uint8_t SERVO_PIN = 1; // servo signal
/* Input Pins:*/
const uint8_t BTN_PIN = 26; // D0 / A0
const uint8_t ENDSTOP_TOP = 4; // D9 / MISO
const uint8_t ENDSTOP_BOTTOM = 2; // bottom switch
/* --- Motion Constants --- */
const uint16_t STEP_DELAY_US = 1000000UL / 200; // 200 half-steps/sec
const uint16_t HALF_STEPS_PER_REV = 400;
const float LEAD_MM = 8.0;
const float TRAVEL_MM = 67.0;
const uint16_t MAX_STEPS = uint16_t((TRAVEL_MM/LEAD_MM)*HALF_STEPS_PER_REV);
const uint16_t HOME_STEP_LIMIT = MAX_STEPS + 100; // safety cap
const uint16_t SWIPE_OFFSET_STEPS = 200; // 4 mm = 200 half-steps
const uint8_t SWIPE_ANGLE = 45; // half-sweep
const uint8_t SWIPE_REPEATS = 1;
const uint16_t SWIPE_DELAY_MS = 1000; // ms between sweeps
const uint8_t SERVO_HOME = 90; // startup home
const uint8_t SERVO_REST = 45; // swipe rest
const uint16_t DEBOUNCE_MS = 50;
/* --- Half-step sequence ---
This sequence defines the order in which the motor coils are energized to achieve half-stepping.
Each sub-array corresponds to a half-step, with the four elements representing the state of the two coils (A and B) for each of the two motor phases.
*/
const bool HALF_SEQ[8][4] = {
{1,0,0,0},{1,0,1,0},{0,0,1,0},{0,1,1,0},
{0,1,0,0},{0,1,0,1},{0,0,0,1},{1,0,0,1}
};
uint8_t halfIdx = 0;
Servo swipeServo;
// apply one half-step
void applyHalf(uint8_t idx) {
digitalWrite(IN1A, HALF_SEQ[idx][0]);
digitalWrite(IN1B, HALF_SEQ[idx][1]);
digitalWrite(IN2A, HALF_SEQ[idx][2]);
digitalWrite(IN2B, HALF_SEQ[idx][3]);
}
// turn off all coils
void coilsOff() {
digitalWrite(IN1A, LOW);
digitalWrite(IN1B, LOW);
digitalWrite(IN2A, LOW);
digitalWrite(IN2B, LOW);
}
// returns steps taken until endstop (HIGH→LOW), cw=true for up
uint16_t homeToEndstop(uint8_t pin, bool cw) {
uint16_t steps = 0;
while (digitalRead(pin) == HIGH && steps < HOME_STEP_LIMIT) {
applyHalf(halfIdx);
halfIdx = cw ? (halfIdx + 1) & 7 : (halfIdx + 7) & 7;
delayMicroseconds(STEP_DELAY_US);
steps++;
}
coilsOff();
delay(200);
return steps;
}
// wait for button press, then
void waitForButton(const char* msg) {
Serial.println(msg);
while (digitalRead(BTN_PIN) == HIGH) {}
delay(DEBOUNCE_MS);
while (digitalRead(BTN_PIN) == LOW) {}
delay(DEBOUNCE_MS);
}
/* perform one swipe:
- attach servo
- move to SERVO_REST
- move to SERVO_HOME + SWIPE_ANGLE
- move to SERVO_HOME - SWIPE_ANGLE
- repeats SWIPE_REPEATS times
- move back to SERVO_REST
- detach servo
*/
void swipe() {
if (!swipeServo.attached()) swipeServo.attach(SERVO_PIN);
swipeServo.write(SERVO_REST); delay(200);
for (uint8_t i = 0; i < SWIPE_REPEATS; ++i) {
swipeServo.write(SERVO_HOME + SWIPE_ANGLE);
delay(SWIPE_DELAY_MS);
swipeServo.write(SERVO_HOME - SWIPE_ANGLE);
delay(SWIPE_DELAY_MS);
}
swipeServo.write(SERVO_REST); delay(200);
swipeServo.detach();
}
// clear top switch by moving down SWIPE_OFFSET_STEPS
void clearTopSwitch() {
for (uint16_t i = 0; i < SWIPE_OFFSET_STEPS; ++i) {
applyHalf((halfIdx + 7) & 7);
halfIdx = (halfIdx + 7) & 7;
delayMicroseconds(STEP_DELAY_US);
}
coilsOff();
delay(200);
}
// setup function: initialize pins, attach servo, and home to SERVO_HOME before detaching servo.
void setup() {
Serial.begin(115200);
while (!Serial) {}
pinMode(IN1A, OUTPUT); pinMode(IN1B, OUTPUT);
pinMode(IN2A, OUTPUT); pinMode(IN2B, OUTPUT);
pinMode(BTN_PIN, INPUT_PULLUP);
pinMode(ENDSTOP_TOP, INPUT_PULLUP);
pinMode(ENDSTOP_BOTTOM, INPUT_PULLUP);
// On startup: servo → HOME → detach
swipeServo.attach(SERVO_PIN);
swipeServo.write(SERVO_HOME);
delay(1000);
swipeServo.detach();
Serial.println("Machine ready. Press button to zero at bottom.");
}
// Main loop:
void loop() {
// Step 1: zero at bottom
waitForButton("Press button to zero bottom");
uint16_t bottomSteps = homeToEndstop(ENDSTOP_BOTTOM, false);
Serial.println("Zeroed. Insert cards, then press button to start feeding.");
// Step 2: start feed
waitForButton("Press button to begin feed cycle");
// Defines lasttopSteps to compare with next top steps.
uint16_t lastTopSteps = 0;
// Step 3: feed loop
while (true) {
// home up
uint16_t topSteps = homeToEndstop(ENDSTOP_TOP, true);
Serial.print("Top steps: "); Serial.println(topSteps);
// moves down to clear top switch
clearTopSwitch();
// swipe card
swipe();
// step down 4 mm
Serial.println("Advancing 4 mm for next card");
for (uint16_t i = 0; i < SWIPE_OFFSET_STEPS; ++i) {
applyHalf((halfIdx + 7) & 7);
halfIdx = (halfIdx + 7) & 7;
delayMicroseconds(STEP_DELAY_US);
}
coilsOff();
delay(200);
// compares topSteps with lastTopSteps to determine if the feed cycle should continue
if (topSteps <= lastTopSteps) {
Serial.println("No more cards or feed error. Stopping.");
break;
}
lastTopSteps = topSteps;
}
Serial.println("Feed cycle complete.");
while (1);
}
What questions were answered?¶
-
Is it possible to make an automated scanning machine significantly cheaper than pre-existing industrial solutions?
The version of the machine I presented whilst not nearly as perfect as the high-end industrial models serves my needs as an individual. I don’t need automatic sorting or pixel-perfect card condition checking. I just need a machine that handles the moving of cards for me so I can do something else while it dumps the cards in front of the scanner.
-
How reliably can the machine dispense cards?
As of writing this the current record is 38 cards scanned succcessfully before a misfeed. The machine detects the position error and stops. So it’s not perfect but it’s definitely getting there.
-
Did this actually save me time?
That remains to be seen. Once this thing gets reliable enough I’ll feed it my entire collection and see how that goes. I’ll need to do a time comparison of the scanning rate doing it manually vs keeping the machine fed with cards. The end result should hopefully be a “Set and forget” process.
What worked? What didn’t?¶
Detecting the thickness of the stack and positioning the platform using the endstop worked way better than I had ever expected. The servo swiper is the main issue with the current iteration as the cards vary in friction, texture and thickness. Occasional misfeeds cause cards to only partially eject.
How was it evaluated?¶
Due to time limitations I didn’t manage to do very much testing but so far the machine is reliable enough to go through 20-30 cards before a hiccup. The thing I’m most satisfied with is the fact that I haven’t managed to damage any cards so far. In Applications & Implications I posited that the machine should be evaluated of how well it demonstrates knowledge of the principles used. I feel I showed knowledge and improvement in the disciplines used. The CAD for the stepper arm is modular and allows for positioning adjustments in hardware rather than requiring re-printing each component. The integration of mechanical components I designed with ones I didn’t is polished and the PCB shows everything I’ve learned during FabAcademy including a voltage divider for current limiting the DRV8251A and a common fill ground plane for ease of routing and cooling the drivers. There are parts of the machine I feel could have been improved had I more time but overall I am very satisfied with the outcome so far.
What are the implications?¶
For the time being. This is a machine I intend to continue developing for personal use but maybe one day. If it reaches a level of polish I’m satisfied with I’ll publish this to sharing sites like Printables.com or Instructables.com. I’ve licensed the project under a CC BY-SA 4.0 Licens. I go over the thought process in 19. Invention, Intellectual Property & Income..