Bafang USB Programming Adapter

Changing (“programming”) the Bafang BBS02 motor controller parameters requires a USB-to-serial adapter with a connector matching the end of the cable from the motor to the display. While you can buy such things directly from the usual randomly named Amazon sellers, I happen to have a wide variety of bare adapter boards, so I just bought a display extender cable and cut it in half to get the connector; you can apparently buy pigtailed connectors (for more than the price of an extender) if you dislike cutting cables in half.

Various documents provide versions of the canonical illustration of the motor end of the display cable, as ripped from Penoff’s original documentation:

Bafang BBS02 display cable pinout
Bafang BBS02 display cable pinout

The pin colors correspond to the wiring inside the motor cable, but the extender uses different colors, because nobody will ever know:

Bafang programmer - wire colors
Bafang programmer – wire colors

A bit of work with a continuity meter gave the pinout:

Bafang BBS02 display extender - wire colors
Bafang BBS02 display extender – wire colors

Don’t trust stuff you read on the Intertubes: make your own measurements and draw your own diagrams!

You want the cable end carrying the sockets to mate with the pins on the motor cable (coming in from the left):

Bafang programmer - cable ends
Bafang programmer – cable ends

Soldering the cable to a known-counterfeit FTDI USB adapter went swimmingly:

Bafang programmer - USB adapter wiring
Bafang programmer – USB adapter wiring

Note that the yellow-blue connection carries the full 48 V from the battery and may or may not have any current limiting / fusing / protection, so be a little more careful than usual in your wiring layout.

The red jumper from DTR to CTS, shown in all the Amazon and eBay listIngs, turns out to be unnecessary.

A quick and dirty case (eventually held together with generous hot-melt glue blobs) protects the PCB and armors the cables:

Bafang USB-serial adapter interior
Bafang USB-serial adapter interior

The solid model over on the right looks about like you’d expect:

Bafang Battery Mount - complete build view
Bafang Battery Mount – complete build view

Most of the instructions will tell you to hot-plug the cable to the motor with the battery connected, which strikes me as foolhardy; not all of those pins make contact in the right order, which means you will slap 50-odd volts across the wrong parts of the circuitry.


  • Disconnect the battery
  • Unplug the display
  • Plug the adapter cable into the motor connector
  • Plug the USB cable into the Token Windows Laptop
  • Reconnect the battery
  • Fire up the “programming” routine
  • Send the new configuration to the motor controller
  • Disconnect the battery
  • Unplug the adapter cable
  • Reconnect the display cable
  • Reconnect the battery

Makes more sense to me, even if it’s more tedious.

Tuck this OpenSCAD source code for the case into the original program that produces the battery mounts:

Layout = "Build";               // [Frame,Block,Show,Build,Bushing,Cateye,Case]

… snippage …

// Programming cable case

ProgCavity = [70.0,19.0,10.0];
ProgBlock = [85.0,25.0,15.0];
ProgCableOD = 4.0;

module ProgrammerCase() {

    difference() {
        hull() {
            for (i=[-1,1], j=[-1,1])
                translate([i*(ProgBlock.x/2 - CornerRadius),j*i*(ProgBlock.y/2 - CornerRadius),-ProgBlock.z/2])

// Half case sections for printing

module HalfCase(Section = "Upper") {

    intersection() {
        if (Section == "Upper")

… snippage …

// tuck this into the Build conditional

    translate([0,3*Block.x,0]) {


Tour Easy: Bafang Brake Sensors

Over the decades, we have devoted considerable time and attention to adjusting the reach and travel of the brake levers on Mary’s bike, so I ordered a pair of brake sensors for the Bafang BBS02 motor to mount on the existing hardware:

Tour Easy Bafang BBS02 - brake sensor - installed
Tour Easy Bafang BBS02 – brake sensor – installed

The sensor is the black block secured to the brake mount (with good outdoor foam tape), with the bar magnet similar secured to the handle. The magnet ended up slightly off-center from the switch due to the overlapping joint between the lever and the mount; I can’t detect any difference from having it centered.

The Bafang switches included cute little disk-shaped neodymium magnets which weren’t suited for the levers and stuck out in all directions without getting particularly close to the sensor. As a result, the least pressure on the brake handle produced a hair-trigger switch activation.

So I harvested two bar-shaped magnets from a defunct Philips Sonicare toothbrush head, reducing the rather large assortment I’ve been saving for just such an occasion by one item. Each brush head contains a pair magnets attached to a steel backing plate, seen here after removing the lower magnet:

Tour Easy Bafang BBS02 - brake sensor - donor magnet assembly
Tour Easy Bafang BBS02 – brake sensor – donor magnet assembly

I don’t know how Philips attaches the magnets, but a few shots to the steel backing plate with a drift punch breaks the bond without any obvious damage:

Tour Easy Bafang BBS02 - brake sensor - donor magnet loosened
Tour Easy Bafang BBS02 – brake sensor – donor magnet loosened

Neodymium magnets have a nickel plating to prevent corrosion, but AFAICT the only way to know whether I’ve cracked the plating is waiting to see if the magnet falls apart. If it does, I promise to be more careful with the next toothbrush head.

They’re magnetized through the thinnest section, not along the length like an old-school bar magnet, but the disk magnets are similarly magnetized and I think the net effect is about the same.

The bars fit the brake handles more closely, put more of the magnet closer to the switch, and allow about 5 mm of travel before tripping the switch.

Pending more road testing, the switches seem more usable.

Protip 1: Demagnetize your tools after working with neodymium magnets.

Protip 2: Don’t put a loose magnet anywhere near your bench block, because it will shatter when it snaps onto the block from a surprising distance.

Tour Easy: Bafang Shift Sensor

The shift sensor detects motion of the rear derailleur cable so the Bafang BBS02 can briefly cut motor power while the chain moves across the sprockets:

Tour Easy Bafang BBS02 - shift sensor - installed
Tour Easy Bafang BBS02 – shift sensor – installed

This should be a drop-in fit on most bikes, but the Tour Easy’s front brazed cable stop is a little shorter than the ferrule. Trimming a plastic tube poses little problem:

Tour Easy Bafang BBS02 - shift sensor - bushing
Tour Easy Bafang BBS02 – shift sensor – bushing

The ferrule now fits neatly in the stop, although the sensor casing sits at a slight angle because the stop’s centerline puts the cable slightly closer to the frame than the back of the sensor body will allow. You could mount it elsewhere, but the cable stop sits directly above the motor and doesn’t require an extension cable.

The sensor works wonderfully well, with the motor pausing for perhaps a second during the shift: just shift normally and it’s done.

A red LED (the small dot to the right of the label) blinks when the sensor detects a shift, so you can verify its operation on the work stand.

Tour Easy: Bafang 48 V 11.6 A·h Battery Mount

Bafang BBS02 batteries should mount on the water bottle bosses along a more-or-less standard bicycle’s downtube, which a Tour Easy recumbent has only in vestigial form. The battery does, however, fit perfectly along the lower frame tubes:

Tour Easy Bafang mid-drive - battery
Tour Easy Bafang mid-drive – battery

You might be forgiven for thinking Gardner Martin (not to be confused with Martin Gardner of Scientific American fame) designed the Tour Easy frame specifically to hold that battery, but the design dates back to the 1970s and it’s just a convenient coincidence.

The battery slides into a flat baseplate and locks in place, although it’s definitely not a high-security design. Mostly, the lock suffices to keep honest people honest and prevent the battery from vibrating loose while riding:

Tour Easy Bafang battery mount - baseplate installed
Tour Easy Bafang battery mount – baseplate installed

The flat enclosure toward the rear was obviously designed for more complex circuitry than it now contains:

Tour Easy Bafang battery mount - interior
Tour Easy Bafang battery mount – interior

Those are all neatly drilled and tapped M3 machine screw holes. The cable has no strain relief, despite the presence of suitable holes at the rear opening. I tucked the spare cable inside, rather than cut it shorter, under the perhaps unwarranted assumption they did a good job crimping / soldering the wires to the terminals.

The red frame tubes are not parallel, so each of the four mounting blocks fits in only one location. They’re identified by the side-to-side tube measurement at their centerline and directional pointers:

Bafang Battery Mount - Show bottom
Bafang Battery Mount – Show bottom

The first three blocks have a hole for the mounting screw through the battery plate. The central slot fits around the plate’s feature for the recessed screw head. The two other slots clear the claws extending downward from the battery into the plate:

Bafang Battery Mount - Show view
Bafang Battery Mount – Show view

The rear block has a flat top and a recessed screw head, because the fancy metal enclosure doesn’t have a screw hole:

Tour Easy Bafang battery mount - top detail
Tour Easy Bafang battery mount – top detail

I thought of drilling a hole through the plate, but eventually put a layer of carpet tape atop the block to encourage it to not slap around, as the whole affair isn’t particularly bendy. We’ll see how well it works on the road.

I had intended to put an aluminum plate across the bottom to distribute the clamping force from the screw, but found a suitable scrap of the institutional-grade cafeteria tray we used as a garden cart seat:

Tour Easy Bafang battery mount - bottom detail
Tour Easy Bafang battery mount – bottom detail

I traced around the block, bandsawed pretty close to the line, then introduced it to Mr Disk Sander for final shaping.

The round cable runs from the rear wheel speed sensor through all four blocks to join the motor near the bottom bracket. Because a recumbent bike’s rear wheel is much further from its bottom bracket, what you see is actually an extension cable with a few extra inches doubled around its connection just ahead of the battery.

Each of the four blocks takes about an hour to print, so I did them individually while making continuous process improvements to the solid model:

Bafang Battery Mount - Build view
Bafang Battery Mount – Build view

The heavy battery cable runs along the outside of the left frame tube, with enough cable ties to keep it from flopping around:

Tour Easy Bafang battery mount - bottom view
Tour Easy Bafang battery mount – bottom view

I wanted to fit it between the tubes, but there just wasn’t enough room around the screw in the front block where the tubes converge. It’s still pretty well protected and should be fine.

The chainline worked out much better than I expected:

Tour Easy Bafang battery mount - chainline
Tour Easy Bafang battery mount – chainline

That’s with the chain on the lowest (most inboard) rear sprocket, so it’s as close to the battery as it gets. I’m sure the battery will accumulate oily chain grime, as does everything else on a bike.

Lithium batteries have a vastly higher power density than good old lead acid batteries, but seven pounds is still a lot of weight!

The OpenSCAD source code as a GitHub Gist:

// Tour Easy Bafang Battery Mount
// Ed Nisley KE4ZNU 2021-04
Layout = "Build"; // [Frame,Block,Show,Build,Bushing,Cateye]
FrameWidths = [60.8,62.0,63.4,66.7]; // last = rear overhang support block
Support = true;
//- Extrusion parameters must match reality!
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
Protrusion = 0.1; // make holes end cleanly
inch = 25.4;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
ID = 0;
OD = 1;
// Dimensions
// Bike frame lies along X axis, rear to +X
FrameTube = [350,22.6 + HoleWindage,22.6 + HoleWindage]; // X = longer than anything else
FrameAngle = atan((65.8 - 59.4)/300); // measured distances = included angle between tubes
TubeAngle = FrameAngle/2; // .. frame axis to tube
FrameSides = 24;
echo(str("Frame angle: ",FrameAngle));
SpeedOD = 3.5; // speed sensor cable along frame
PowerOD = 6.7; // power cable between frame tubes
BatteryBoss = [5.5,16.0,2.5]; // battery mount boss, center is round
BossSlotOAL = 32.0; // .. end bosses are elongated
BossOC = 65.0; // .. along length of mount
LatchWidth = 10.0; // battery latches to mount plate
LatchThick = 1.5;
LatchOC = 56.0;
WallThick = 5.0; // thinnest wall
Block = [25.0,78.0,FrameTube.z + 2*WallThick]; // must be larger than frame tube spacing
echo(str("Block: ",Block));
// M5 SHCS nyloc nut
Screw = [5.0,8.5,5.0]; // OD, LENGTH = head
Washer = [5.5,10.1,1.0];
Nut = [5.0,9.0,5.0];
// 10-32 Philips nyloc nut
Screw10 = [5.2,9.8,3.6]; // OD, LENGTH = head
Washer10 = [5.5,11.0,1.0];
Nut10 = [5.2,10.7,6.2];
Kerf = 1.0; // cut through middle to apply compression
CornerRadius = 5.0;
EmbossDepth = 2*ThreadThick; // lettering depth
// Useful routines
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
FixDia = Dia / cos(180/Sides);
cylinder(d=(FixDia + HoleWindage),h=Height,$fn=Sides);
// clamp overall shape
module ClampBlock() {
difference() {
for (i=[-1,1], j=[-1,1])
translate([i*(Block.x/2 - CornerRadius),j*(Block.y/2 - CornerRadius),-Block.z/2])
translate([0,0,-(Block.z/2 + Protrusion)])
PolyCyl(Screw[ID],Block.z + 2*Protrusion,6);
translate([0,-(Block.y/2 - PowerOD + Protrusion/2),-PowerOD/2])
cube([2*Block.x,2*PowerOD + Protrusion,PowerOD],center=true);
// frame tube layout with measured side-to-side width
module Frame(Outer = FrameWidths[0],AdjustDia = 0.0) {
TubeOC = Outer - FrameTube.y/cos(TubeAngle); // increase dia for angle
for (i=[-1,1])
rotate([0,90,i*TubeAngle]) rotate(180/FrameSides)
cylinder(d=FrameTube.z + AdjustDia,h=FrameTube.x,center=true,$fn=FrameSides);
// complete clamp block
module Clamp(Outer = FrameWidths[0]) {
TubeOC = Outer - FrameTube.y/cos(TubeAngle); // increase dia for angle
difference() {
translate([0,(TubeOC/2 - FrameTube[OD]/2),-SpeedOD/2])
translate([0,15,Block.z/2 - EmbossDepth/2 + Protrusion])
translate([0,22,-Block.z/2 + EmbossDepth/2 - Protrusion])
if (Outer == FrameWidths[len(FrameWidths) - 1]) { // special rear block
translate([0,0,Block.z/2 - 2*Screw10[LENGTH]])
PolyCyl(Washer10[OD],2*Screw10[LENGTH] + Protrusion,6);
else { // other blocks have channels
translate([0,0,Block.z/2 - BatteryBoss[LENGTH]/2 + Protrusion])
cube([BossSlotOAL,BatteryBoss[OD],BatteryBoss[LENGTH] + Protrusion],center=true);
for (i=[-1,1])
translate([0,i*LatchOC/2,Block.z/2 - LatchThick/2 + Protrusion])
cube([BossSlotOAL,LatchWidth,LatchThick + Protrusion],center=true);
translate([0,15,Block.z/2 - EmbossDepth])
text(text="^",size=5,spacing=1.00,font="Bitstream Vera Sans:style=Bold",
rotate(-90) mirror([0,1,0])
text(text=str("^ ",Outer),size=4.5,spacing=1.00,font="Bitstream Vera Sans:style=Bold",
if (Support)
color("Yellow") {
NumRibs = 7;
RibOC = Block.x/(NumRibs - 1);
intersection() {
translate([0,0,Block.z/2 + Kerf/2])
union() for (j=[-1,1]) {
cube([1.1*Block.x,FrameTube.y - 2*ThreadThick,4*ThreadThick],center=true);
for (i=[-floor(NumRibs/2):floor(NumRibs/2)])
rotate([0,90,0]) rotate(180/FrameSides)
cylinder(d=FrameTube.z - 2*ThreadThick,h=2*ThreadWidth,$fn=FrameSides,center=true);
// Half clamp sections for printing
module HalfClamp(i = 0, Section = "Upper") {
intersection() {
if (Section == "Upper")
// Handlebar bushing for controller
BushingSize = [16.0,22.2,15.0];
module Bushing() {
difference() {
translate([0*(BushingSize[OD] - BushingSize[ID])/4,0,BushingSize[LENGTH]/2])
// Cateye cadence sensor bracket
module Cateye() {
Pivot = [3.0,10.0,8.0];
Slot = [4.2,14.0,14.0];
Clip = [8.0,Slot.y,Slot.z + Pivot[OD]/2];
difference() {
union() {
rotate([0,90,0]) rotate(180/6)
translate([0,0,-(Clip.z - Slot.z/2)])
cube(Slot + [0,Protrusion,Protrusion],center=true);
// Build them
if (Layout == "Frame")
if (Layout == "Block")
if (Layout == "Bushing")
if (Layout == "Cateye")
if (Layout == "Upper" || Layout == "Lower")
if (Layout == "Show") {
color("Red", 0.3)
if (Layout == "Build") {
n = len(FrameWidths);
gap = 1.2;
for (i=[0:n-1]) {
j = i - ceil((n-1)/2);

Tour Easy: Bafang BBS02 Configuration

The Bafang BBS02 motor claims a 750 W power output, although I suspect that’s measured at the instant before it flings its guts across the test lab:

Tour Easy Bafang BBS02 motor
Tour Easy Bafang BBS02 motor

With a nominal 48 V battery supplying the motor’s nominal 24 A (some say 25 A) current, it dissipates well over 1100 W, although that’s obviously a short-term thing. With 750 W calling for 15-ish A, most likely it will (ideally) suffer thermal shutdown long before the battery runs out.

Torque being more-or-less proportional to current, its nominal 160 N·m torque at 24 A scales downward by the same factor as the current, for 100 N·m at 15 A.

The as-received Bafang BBS02 motor controller configuration provided far too much torque for our riding style; I think it’s intended for much younger folks tackling off-road trails on what used to be called mountain bikes, rather than assisting us with normal street riding.

For example, the default maximum current was 24 A and the first step of pedal assistance was 28% = 6.7 A → 45 N·m: a pretty hefty shove right off the starting line. The Tour Easy was pretty much uncontrollable in the driveway, which is a Bad Sign.

I started with the “Limitless” configuration (wherein the assistance for all steps continues up to the 20 mph overall speed limit) and reduced the maximum current to 15 A.

The first assistance step of 5% = 0.8 A → 5 N·m now compensates for the additional weight of the Bafang motor + battery and feels like the unloaded bike.

The second step was 37% = 8.9 A → 59 N·m and is now 7% = 1 A → 7 N·m, so Mary can ride along with a little oomph for minor hills.

The third step was 46% = 11 A → 74 N·m and is now 16% = 2.4 A → 16 N·m, enough for the admittedly gentle hills along Vassar Road.

The throttle uses the ninth step setting (100% = 15 A → 100 N·m) to provide a “get out of Dodge” boost at intersections.

So far, the BBS02 configuration file looks like this:

[Pedal Assist]
[Throttle Handle]

Mary says she’s getting entirely enough exercise and, frankly, so am I. We have yet to try faster paces and steeper hills.

Tour Easy: Bafang BBS02 Mid-Drive Motor

For reasons not relevant here, Mary’s Tour Easy recumbent now sports a Bafang BBS02 Mid-drive motor:

Tour Easy Bafang mid-drive - overview
Tour Easy Bafang mid-drive – overview

It pretty much Just Fit, although the lithium battery sits atop mounts conjured from the vasty digital deep:

Tour Easy Bafang mid-drive - battery
Tour Easy Bafang mid-drive – battery

Many cables connect all the doodads, which a custom-made e-bike can hide inside the frame, but … that’s not an option for us.

The Bafang BBS02 kit is basically plug-n-play, at least if you own a standard-ish bike. I included some useful options for our setup:

Changing the controller parameters, usually called “programming”, required firing up the Token Windows Laptop:

As you might expect, I set up a relatively sedate and low-powered pedal assist mode in place of the default rocket sled mode.

The motor design seems a decade old, so Bafang (née 8Fun) has had time to work out some of the original design misfeatures. It definitely has shortcomings, but nothing insurmountable so far.

Early results suggest Mary is now riding her familiar bike over much flatter terrain.

Some background reading:

More on all of this as I compile my notes …

Tour Easy Rear Fender Bracket: More Cable Clearance

Most likely due to the fiddling around the larger rear brake noodle, the 3D printed bracket holding the fender to the frame failed:

Tour Easy Rear Fender Bracket - failed joint
Tour Easy Rear Fender Bracket – failed joint

Hey, it lasted for six years.

Making another one just like the other one, but with a little more clearance for the brake cable fittings, required a few tweaks to the solid model:

Rear Fender Bracket - more clearance
Rear Fender Bracket – more clearance

It’s slightly less chunky and holds the fender a bit closer to the tire:

Tour Easy Rear Fender Bracket - new vs old clearance
Tour Easy Rear Fender Bracket – new vs old clearance

The piece over on the left cupping the fender wasn’t broken, so I scuffed up the mating surfaces, applied a layer of JB Plastic Bonder (my new go-to adhesive for printed stuff), clamped it overnight, and it looked OK.

While that was curing, I shortened the screw holding the clamp to the bike frame:

Tour Easy Rear Fender Bracket - cutoff wheel dust collection
Tour Easy Rear Fender Bracket – cutoff wheel dust collection

The shop vac nozzle does a great job of collecting all the abrasive dust; highly recommended.

Because I had a dollop of adhesive left over, I applied a 1.8 mm drill (from a set of metric bits I’d been meaning to buy for far too long) to the screw:

Tour Easy Rear Fender Bracket - screw drilling
Tour Easy Rear Fender Bracket – screw drilling

And glued a snippet of pretty blue PETG filament in the hole:

Tour Easy Rear Fender Bracket - frame screw PETG insert
Tour Easy Rear Fender Bracket – frame screw PETG insert

As far as I can tell, this will have no effect on the screw’s goodness, but it makes me feel better about crunching it onto the frame.

Installation goes like you’d expect and there’s now enough clearance to keep the brake hardware off the bracket:

Tour Easy Rear Fender Bracket - installed
Tour Easy Rear Fender Bracket – installed

I replaced the boot while installing the larger noodle; perhaps I should have trimmed most of it away.

The riding season is upon us!