I recently bought 20 Sanyo NCR18650GA Lithium batteries. 12 of them will be used for developing a BMS for a upcoming E-bike project. From the other cells I will put together 2 battery packs for my bicycle lights, 4 cells each in 2s2p configuration.
This type of cells are usually spot welded together using thin nickel strips. I’ve read that many have been successful soldering the cells together but how fun is that when you can build a DIY battery spot welder.
Most designs that I have found is based on a SSR (Solid State Relay) controlled MOT (Microwave Oven Transformer) with the secondary replaced by few turns of heavy wire which converts mains voltage to high welding current. This doesn’t seem like a good solution since the current comes in 100 Hz pulses which makes welding energy control very difficult. I know some SSR can only break the current at the zero-crossing-point of the mains voltage resulting in a pulse time resolution of 10 ms. I don’t think this is good enough to get a consistent result.
There are also variants which discharge a large capacitor bank through a MOSFET which seems like it would give a much higher degree of control. I also found a similar design using a car starter battery instead of capacitors which seemed even more interesting.
I don’t have any spare car batteries, instead I’ll use some high power LiPo batteries. I have a pack of 4 Turnigy 6S 20C 5Ah batteris that I could connect in parallell. This will result in a 6s4p 20C 20Ah pack capable of delivering ~20 V 400 A continuous. It will not have any problem delivering enough current for battery tab welding in short ~10 ms pulses.
The main focus of the build was to use as much parts from my junk-bin as possible.
Electrodes
The electrodes are built om 10 mm copper rods sharpened in one end and threaded with an M10 thread in the other. On the threaded end a 25 mm² welding cable are connected. To set off the welding pulse I have placed a small button on the top of one electrode.


Electronics
To switch the current I found six FDP8440 MOSFETs rated at 40 V with a very low RDSon of 2.2 mΩ. If the welding current reach 1200 A they will handle 200 A each resulting in ~90W losses. This will easily be handled for a few hundredths of a second every 10 s or so. Especially since they are mounted on a thick copper busbar.

The mosfets will be controlled by a Microchip TC4421 from a 8-bit PIC microcontroller. Haven’t used a 8-bit PIC in ages, nowdays i prefer ARM Cortex-M processors, mainly the STM32 and LPC series. Since I’m building this on a veroboard I needed a DIP-casing so I decided to use an ancient PIC16F648A from the junkbin. This processor was perfect for a quick job like this, extremely simple keeping the datasheet reeding to a minimum

It has an internal 4 MHz 1% oscillator which will work fine for this application since there are no asynchronous communication, and 1% precision of the pulse timing is more than enough, no need for an external crystal oscillator. The reset-pin can be turned off with the config bits and all I/O-pins on Port B have internal pull-ups saving me a few resistors.
To program the processor I used Microchip MPLAB X IDE, XC8 compiler, and a PICkit 2 programmer. I’ve never used MPLAB X before, but it worked rather well. This is the application firmware, perhaps a little bit overdone, but why not?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
/********************************** * Copyright Linus Helgesson 2016 * **********************************/ #include #include // CONFIG #pragma config FOSC = INTOSCIO // Oscillator Selection bits (INTOSC oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled) #pragma config MCLRE = OFF // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is digital input, MCLR internally tied to VDD) #pragma config BOREN = OFF // Brown-out Detect Enable bit (BOD disabled) #pragma config LVP = OFF // Low-Voltage Programming Enable bit (RB4/PGM pin has digital I/O function, HV on MCLR must be used for programming) #pragma config CPD = OFF // Data EE Memory Code Protection bit (Data memory code protection off) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) #define NULL (0) typedef enum { STATE_IDLE = 0, STATE_PULSE_ONE, STATE_PULSE_WAIT, STATE_PULSE_TWO, STATE_COOLDOWN, NUMBER_OF_STATES } state_t; typedef enum { EVENT_BUTTON_PUSHED = 0, EVENT_TIMEOUT } event_t; typedef struct { event_t transitionEvent; state_t nextState; uint16_t timeout; void (*stateFcn)(void); } stateTable_t; // Local function declaration static void pulseEnabled(void); static void pulseDisabled(void); static void startTimer(uint16_t timeout); static void stateMachine(event_t event); // State table with one entry per state. Timeout is specified in 8 us steps. static stateTable_t stateTable[NUMBER_OF_STATES] = { // Transition event, Next state, Timeout, State function {EVENT_BUTTON_PUSHED, STATE_PULSE_ONE, NULL, NULL }, // STATE_IDLE {EVENT_TIMEOUT, STATE_PULSE_WAIT, 150, pulseEnabled }, // STATE_PULSE_ONE {EVENT_TIMEOUT, STATE_PULSE_TWO, 1200, pulseDisabled }, // STATE_PULSE_WAIT {EVENT_TIMEOUT, STATE_COOLDOWN, 1200, pulseEnabled }, // STATE_PULSE_TWO {EVENT_TIMEOUT, STATE_IDLE, 65535, pulseDisabled } // STATE_COOLDOWN }; static volatile state_t currentState = STATE_IDLE; void main(void) { // Init Port B OPTION_REGbits.nRBPU = 0; // Enable PortB internal pull-ups TRISBbits.TRISB1 = 0; // Set RB1 to output pulseDisabled(); // Start by disabling pulse // Init timer T1CONbits.T1CKPS = 0b11; // Prescaler 1:8 PIE1bits.TMR1IE = 1; // Enable Timer interrupt // Main loop while(1) { // Poll pushbutton if(PORTBbits.RB0 == 0) { // Trigger button event stateMachine(EVENT_BUTTON_PUSHED); } // Poll Timer1 if(PIR1bits.TMR1IF) { // Stop timer and clear interrupt T1CONbits.TMR1ON = 0; PIR1bits.TMR1IF = 0; // Trigger timeout event stateMachine(EVENT_TIMEOUT); } } } // Set timer to trigger interrupt after [timeout] 8 us static void startTimer(uint16_t timeout) { uint16_t tmrVal; T1CONbits.TMR1ON = 0; tmrVal = 65535 - timeout; TMR1L = (uint8_t)tmrVal; TMR1H = (uint8_t)(tmrVal >> 8); T1CONbits.TMR1ON = 1; } // Handle state machine static void stateMachine(event_t event) { // Check if state transition should be performed if(stateTable[currentState].transitionEvent == event) { // Go to next state currentState = stateTable[currentState].nextState; // Execute state function if not NULL if(stateTable[currentState].stateFcn != NULL) stateTable[currentState].stateFcn(); // Start timer if time is not NULL if(stateTable[currentState].timeout != NULL) startTimer(stateTable[currentState].timeout); } } // Enable MOSFET output static void pulseEnabled(void) { PORTBbits.RB1 = 0; } // Disable MOSFET output static void pulseDisabled(void) { PORTBbits.RB1 = 1; } |
The pulse length is currently hard coded in the application, it’s easy enough to update. If needed i will later add a switch with a few presets. The microcontroller has no A/D-converter, otherwise a potentiometer for setting this would have been nice.

There is a lot to read about pulse welding online, essentially the first pulse is to break any oxide layers and the second performs most of the welding. I have set pulse 2 to 10 ms, and pulse 1 to 1,25 ms which works fine for 8 x 0,18 mm Nickel strips.
I haven’t received the nickel strips I ordered yet, so my first test subject was some pieces of box cutter blades. It worked perfectly, but perhaps I need to shorten the pulses a little with the much thinner nickel strips.


Future plans
I will use this to build a few batteries, if the design works well, I’ll probably make a new PCB with a display, rotary encoder and a more modern processor.