BAFANG BBSxx change settings on the fly (aka 'cop button') and BBSxx communication protocol

MCC

1 µW
Joined
Jun 21, 2018
Messages
4
A few weeks ago I have bought a 500W 48V (DPC-18 Display) BBS02B kit to make my daily commute (15km one direction) a little bit easier. I'm very happy with the kit, already cycled more than 700 km's in a few weeks.

The thing is, this set is not entirely legal in the Netherlands. So I wanted to have a discrete and easy way to limit the speed of the bicycle (to say max 27 km/h) (not that I am naive enough to think that this would make things legal.. I just liked the challenge ;) ). I was triggered by the fact that serial protocol is used to program the BBS controller.

I have recently started to learn working with Arduino's, and I thought: what if I place an Arduino between the display and the controller that simply forwards all comunnication between the two and when needed, it can send commands to the controller to program another speed limit. To do so, I have build the following circuit:

schemav2.jpg

I have used a RobotDyn Mega 2560 PRO MINI (https://robotdyn.com/mega-2560-pro-mini-atmega2560-16au.html) with the ATmega2560 chip. The RobotDyn is small and the ATmega2560 has a total of four hardware serial ports. I used a Hall sensor as an "invisible" switch to switch between unlimited speed ('by displays command') and limited speed (27 km/h). I used a display extension cable, to make the connections between the display and controller. I power the arduino using a 5v usb output I have on the battery pack. The final project looks like this:
20180530_100851.jpg
20180530_100902.jpg
20180530_101029.jpg

I have glued a metal ring inside the box where the hall sensor is located. This metal ring holds the magnet in place on the outside of the box:

20180530_101007.jpg

I have added a bluetooth HC05 module to monitor the traffic between the display and the controller and it gives me a way to interact with the Arduino and controller.

So, how does it work? With the box between the controller and display, the BBS works just like normal, all communication is just being forwarded between the display and the controller. When I take away the magnet, the Arduino programs predefined settings (from the Pedal Assist tab on Penoff's tool) where speed limit is set to 27km/h. The arduino follows this sequence:
- If the display is sending any command,wait for it to finish
- program the pedal assist settings and check confirmation by the controller
- change to PAS level 0
- go back to previous PAS level

In order for the settings to be effecitve (with the system on and while cycling), the PAS level has to be changed. Thats what the last two steps are for (go to PAS 0 en restore previous PAS level).

To program the Pedal Assist settings, I send the follolwing (HEX) command to the controller:
Code:
16 53 0B 03 FF FF 14 04 04 FF 14 08 00 3C D2

16 53 is the command for programming the Pedal Assist settings
0B 03 FF FF 14 04 04 FF 14 08 00 3C are the settings
D2 is a checksum bit.

Out off lazyness, I have used Penoff's software, to generate the command for me. I installed Com0Com (http://com0com.sourceforge.net/) and setup a virtual Com-port pair. On one of the two paired com-ports, I connect with a terminal (such as Hterm), on the other port of the pair I connect Pennoff's tool. Penoff's software is trying to connect with the controller (sending
Code:
11 51 04 B0 05
to receive the general controller information). Using hterm, I respond:
Code:
51 10 48 5A 58 54 53 5A 5A 36 32 32 32 30 31 31 01 14 1B
Pennoff's tool now believes it is connected the controller (the response code contains general info about the controller type such as manufacturer, model, hardware version, firmware version etc.)

If you then go to the Pedal Assist tab, adjust the settings to your wishes and press "write", the software sends the command to the controller. You can capture this in the hterm terminal:

Knipsel.JPG

This way, I generated the command to program unlimited speed, and limited speed. Of course, you can also learn how the command is structured and build the command yourself.

In the next post, the full Arduino code that I'm currently using and in the post after, I'll describe more about what I have found out about the communication protocol.

Sources I have used:
https://github.com/philippsandhaus/bafang-python
https://endless-sphere.com/forums/viewtopic.php?f=2&t=74692&p=1207355
https://gitlab.com/bafang
https://penoff.me/2016/01/13/e-bike-conversion-software/
http://com0com.sourceforge.net/
https://chromium.googlesource.com/apps/libapps/+/master/hterm
 
The Arduino code:

Code:
/*
   BAFANG BBSxx change settings on the fly (aka 'cop button') and BBSxx communication protocol
   Refer to this topic on https://endless-sphere.com for more information:
   https://endless-sphere.com/forums/viewtopic.php?f=2&t=94850&p=1389269
*/

//Commands for BBS controller
const byte Hspeed[] = {0x16, 0x53, 0x0B, 0x03, 0xFF, 0xFF, 0x14, 0x04, 0x04, 0xFF, 0x14, 0x08, 0x00, 0x3C, 0xD2}; //program pedal settings speed:'by displays command'
const byte Lspeed[] = {0x16, 0x53, 0x0B, 0x03, 0xFF, 0x1B, 0x14, 0x04, 0x04, 0xFF, 0x14, 0x08, 0x00, 0x3C, 0xEE}; //program pedal settings speed:27kph
const byte readPed[] = {0x11, 0x53}; // read pedal settings
const byte PAS0[] = {0x16, 0x0B, 0x00, 0x21, 0x00}; //command that display sends when turn off button is pressed


//Hall sensor variables
const int hallPin = 2;      // hall effect sensor pin
int hallState = 0;          // 0=UnlimitedSpeed(with magnet) 1=LimitedSpeed(without magnet), assume UnlimitedSpeed
bool firstHallValue = true; // Ignore first hall sensor reading, otherwise the BBS is programmed on each start-up

//variables for parsing display/controller data
byte DData[2];              //array to hold incomming display data
byte CData[3];              //array to hold incomming controller data
byte controllerResponse[20];//array to hold responses from controller

const byte DSpeed[] = {0x11, 0x20};               //Command that display sends to controller to receive speed data
const byte DAmps[] = {0x11, 0x0A};                //Command that display sends to controller to receive amp data
const byte Dmoving[] = {0x11, 0x31};              //Command that display sends to controller to receive info weather bike is stationarry or not
const byte DBatPerc[] = {0x11, 0x11};             //UNSURE: battery percentage?
const byte Dunknwn[] = {0x11, 0x08};              //UNKNOWN
const byte PASsuccessResp[] = {0x53, 0x0B, 0x5E}; // response from controller when pedal settings were successfully written

// First two bytes of setting PAS level
const byte DPAS[] = {0x16, 0x0B};
// Next two bytes PAS level
const byte DPAS0[] = {0x00, 0x21}; // PAS 0 16 0B 00 21
const byte DPAS1[] = {0x01, 0x22}; // PAS 1 16 0B 01 22
const byte DPAS2[] = {0x0B, 0x2C}; // PAS 2 16 0B 0B 2C
const byte DPAS3[] = {0x0C, 0x2D}; // PAS 3 16 0B 0C 2D
const byte DPAS4[] = {0x0D, 0x2E}; // PAS 4 16 0B 0D 2E
const byte DPAS5[] = {0x02, 0x23}; // PAS 5 16 0B 02 23
const byte DPAS6[] = {0x15, 0x36}; // PAS 6 16 0B 15 36
const byte DPAS7[] = {0x16, 0x37}; // PAS 7 16 0B 16 37
const byte DPAS8[] = {0x17, 0x38}; // PAS 8 16 0B 17 38
const byte DPAS9[] = {0x03, 0x24}; // PAS 9 16 0B 03 24

// Variables for parsing PAS level
bool PASExp = false;                          //True when PAS 'mask'(0x16, 0x0B) received
int PASbyte = 0;                              //counter that keeps track of how many PAS bytes were received (after 0x16, 0x0B)
int PASLevel = -1;                            //int holding PAS level
byte PASreInit[] = {0x16, 0x0B, 0x01, 0x22};  //command for re-initialising PAS level after speed limit change

//Variables for parsing Display commands and controller response
int DataExp = -1;                   //What data is expected from controller: -1=nothing 0=speed, 1=amps, 2=moving, 3=DBatPerc, 4=unknown
int nExpBytes = 0;                  //number of byte expected from controller
int byteNo = 0;                     //number of bytes received from controller
unsigned long  FirstByteTimestamp;  //time when first byte from controller was received
const float wheelsize_m = 2.2 ;     //wheelsize in meters

bool logging = false;   //enable/disable logging over HC05
int whosTalking = 0;    // keep track of who is talking; 0=display, 1=controller

void setup()
{
  Serial1.begin(115200);  //HC-05 module @TX-18 RX19
  Serial2.begin(1200);    //Display DP-C18 @TX14 RX15
  Serial3.begin(1200);    //BBS02 controller @TX16 RX17

  pinMode(hallPin, INPUT); // The hall effect sensor pin as an input
}

void loop()
{
  checkhall();
  readDisplay();
  readController();
  readTerminal();
}

void checkhall() {
  // reading the state of the hall effect sensor pin
  int hallStatetmp = digitalRead(hallPin);

  // Ignore first hall sensor reading, do not program BBS02
  if (firstHallValue) {
    hallState = hallStatetmp;
    firstHallValue = false;
    return; // do nothing
  }

  //Only program controller when hall sensor state changes (not every reading)
  if (hallState != hallStatetmp) {
    hallState = hallStatetmp;

    switch (hallState) {
      case 1: //no magnet
        changeSpeedLimitation(false); //Max 27kph
        break;

      case 0: //magnet present
        changeSpeedLimitation(true); //Unlimited (By displays command)
        break;
    }
  }
}

void changeSpeedLimitation(bool HighSpeed) {
  //to avoid programming controller in de middle of a display command
  //wait until controller starts responding
  while (whosTalking == 0) {
    readDisplay();
    readController();
  }

  //clear messages send by controller
  readControllerResponse(false);


  bool PASsucces = false;

  //program pedal settings, stop while loop when controller confirms success
  while (!PASsucces) {
    switch (HighSpeed) {
      case true: //
        programController(Hspeed, sizeof(Hspeed), true);
        break;
      case false: //
        programController(Lspeed, sizeof(Lspeed), true);
        break;
    }
    //check if controller gave expected response
    if (memcmp ( PASsuccessResp, controllerResponse, 3 ) == 0) {
      PASsucces = true;
    }
  }

  //Re-init current PAS-level
  programController(PAS0, sizeof(PAS0), false);
  programController(PASreInit, sizeof(PASreInit), false);

  //clear messages send by display in the mean time
  flushDisplay();
}

void programController(byte *command, int commandsize, bool echoResponse) {
  //write command to controller
  Serial3.write(command, commandsize);

  //Echo to HC05 (for debugging
  Serial1.print("writing: ");
  for (int i = 0; i < commandsize; i++)
  {
    Serial1.print(command[i], HEX);
  }
  Serial1.println("");

  //read controller response
  readControllerResponse(echoResponse);
}

void readControllerResponse(bool echo) {

  // To capture the variable length response from controller, wait max 'maxWaittime' for a next byte to be received
  // When maxWaittime is exceeded, asume controller finished sending response

  int maxWaittime = 200; //If nothing is received after  200 miliseconds, controller finished sending response
  bool exceeded_maxWaittime = false;
  unsigned long waitUntill;
  int byteNo = 0;

  waitUntill = millis() + maxWaittime;

  //monitor messages from controller until maxwaittime is exceeded
  while (!exceeded_maxWaittime)  {
    if (Serial3.available() > 0) {
      byte cByte = Serial3.read();

      //place received byte in array
      controllerResponse[byteNo] = cByte;

      if (byteNo < 18) {
        byteNo = byteNo + 1;
      }

      waitUntill = millis() + maxWaittime; //Adjust waitUntill
    }

    if (millis() > waitUntill) {
      exceeded_maxWaittime = true;
    }
  }

  //echo to HC05
  if (echo) {
    for (int i = 0; i < byteNo; i++) {
      Serial1.print(controllerResponse[i], HEX);
    }
    Serial1.println("");
  }
}


void flushDisplay() {
  //Read data from Display and do nothing, just clear the buffer
  while (Serial2.available() > 0) {
    byte dummy = Serial2.read();
  }
}

void readController() {
  // Read byte from the controller and send to Display
  if (Serial3.available())
  {
    parseController(Serial3.peek(), millis());
    Serial2.write(Serial3.read());
    whosTalking = 1;
  }
}

void readDisplay()  {

  // Read byte from the Display and send to controller
  if (Serial2.available())
  {
    parseDisplay(Serial2.peek());
    Serial3.write(Serial2.read());
    whosTalking = 0;
  }
}


void parseDisplay(byte Dincomming) {

  //shift bytes
  DData[0] = DData[1];
  DData[1] = Dincomming;

  //check if first two bytes of setting PAS level are received, next two bytes will determine PAS level
  if (memcmp ( DData, DPAS, sizeof(DData) ) == 0) {
    DataExp = -1;
    PASbyte = -1;
    PASExp = true; // next two bytes for setting PAS level are expected
  }


  //Keep track of how many PAS-level bytes are received
  if (PASExp) {
    PASbyte = PASbyte + 1;
  }

  //When two PASlevel bytes are received (after the 0x16, 0x0B mask), parse two PAS-level bytes
  if (PASbyte == 2) {
    PASExp = false; //no longer expecting PAS-level
    PASbyte = -1;//reset
    parsePAS();
  }

  //when logging turned off, skip rest of parsing
  if (!logging) {
    return;
  }


  //compare byte array 'DData' with known commands from display
  if (memcmp ( DData, DSpeed, sizeof(DData) ) == 0) {
    DataExp = 0;
    nExpBytes = 3;
  }
  else if (memcmp ( DData, DAmps, sizeof(DData) ) == 0) {
    DataExp = 1;
    nExpBytes = 2;
  }
  else if (memcmp ( DData, Dmoving, sizeof(DData) ) == 0) {
    DataExp = 2;
    nExpBytes = 2;
  }
  else if (memcmp ( DData, DBatPerc, sizeof(DData) ) == 0) {
    DataExp = 3;
    nExpBytes = 2;
  }
  else if (memcmp ( DData, Dunknwn, sizeof(DData) ) == 0) {
    DataExp = 4;
    nExpBytes = 1;
  }
  else {
    DataExp = -1;
    nExpBytes = 0;
  }

}

void parsePAS() {

  int oldPAS = PASLevel;

  //compare byte array with PAS levels
  if (memcmp ( DData, DPAS0, sizeof(DData) ) == 0) {
    PASLevel = 0;
  }
  else if (memcmp ( DData, DPAS1, sizeof(DData) ) == 0) {
    PASLevel = 1;
  }
  else if (memcmp ( DData, DPAS2, sizeof(DData) ) == 0) {
    PASLevel = 2;
  }
  else if (memcmp ( DData, DPAS3, sizeof(DData) ) == 0) {
    PASLevel = 3;
  }
  else if (memcmp ( DData, DPAS4, sizeof(DData) ) == 0) {
    PASLevel = 4;
  }
  else if (memcmp ( DData, DPAS5, sizeof(DData) ) == 0) {
    PASLevel = 5;
  }
  else if (memcmp ( DData, DPAS6, sizeof(DData) ) == 0) {
    PASLevel = 6;
  }
  else if (memcmp ( DData, DPAS7, sizeof(DData) ) == 0) {
    PASLevel = 7;
  }
  else if (memcmp ( DData, DPAS8, sizeof(DData) ) == 0) {
    PASLevel = 8;
  }
  else if (memcmp ( DData, DPAS9, sizeof(DData) ) == 0) {
    PASLevel = 9;
  }


  // Only do if PAS level actually changed
  if (PASLevel != oldPAS) {
    // set command for re-initialising current PAS level (in case speed limit needs to be changed)
    PASreInit[2] = DData[0];
    PASreInit[3] = DData[1];

    //Echo to HC05
    Serial1.print("*P");
    Serial1.print(PASLevel);
    //Serial1.print(",");
    //Serial1.print(millis());
    Serial1.print("*");
  }
}


void parseController(byte Cincomming, unsigned long timestamp) {
  //when logging turned off, skip procedure
  if (!logging) {
    return;
  }

  //skip procedure if no data is expected
  if (DataExp == -1) {
    byteNo = 0;
    return;
  }

  //timestamp when first byte was received
  if (byteNo == 0) {
    FirstByteTimestamp = timestamp;
  }

  //if bytes are expected, place byte in array and increment byteNo
  if (byteNo < nExpBytes) {
    CData[byteNo] = Cincomming;
    byteNo = byteNo + 1;
  }

  //when the number of expected bytes for a specific display command have been received, parse data
  if (nExpBytes == byteNo) {

    switch (DataExp) {
      case 0: //speed/
        Serial1.print("*S");
        Serial1.print((CData[1] + CData[0] * 256) * wheelsize_m * 60 / 1000); //CData[1]=rpm when rpm>255 CData[0]=1 then 256 should be added to CData[1] for correct rpm
        //Serial1.print(",");
        //Serial1.print(FirstByteTimestamp);
        Serial1.print("*");
        break;
      case 1: //amps
        Serial1.print("*A");
        Serial1.print(CData[0] / 2); // divided by two this seems to be the Amps value
        //Serial1.print(",");
        //Serial1.print(FirstByteTimestamp);
        Serial1.print("*");
        break;
      case 2: //moving
        Serial1.print("*M");
        Serial1.print(CData[0]); //30=stationary, 31=bike is moving
        //Serial1.print(",");
        //Serial1.print(FirstByteTimestamp);
        Serial1.print("*");
        break;
      case 3: //BatteryPercentage (?)
        Serial1.print("*B");
        Serial1.print(CData[0]);
        //  Serial1.print(",");
        // Serial1.print(FirstByteTimestamp);
        Serial1.print("*");
        break;
      case 4: //UNKNOWN
        Serial1.print("*U");
        Serial1.print(CData[0]);
        // Serial1.print(",");
        //Serial1.print(FirstByteTimestamp);
        Serial1.print("*");
        break;
    }
    byteNo = 0;//reset
    DataExp = -1;//reset
  }
}

void readTerminal()  {
  // read commands received from terminal
  if (Serial1.available())
  {
    char terminal =  Serial1.read();

    switch (terminal) {
      case 'l'://switch to limited speed
        changeSpeedLimitation(false);
        break;
      case 'h'://switch to unlimited speed
        changeSpeedLimitation(true);
        break;
      case '0':     // turn logging off
        logging = false;
        break;
      case '1':      // turn logging on
        logging = true;
        break;
    }
  }
}
 
In order to better understand the BBS communication protocol, I started with simply forwarding all traffic between Display and controller and monitor this traffic using the HC05 bluetooth module (monitoring this with a terminal client). This resulted in logs like:
Code:
21145-Display says:
11 20  
21172-Controller says:
0 0 20  
21237-Display says:
11 11  
21263-Controller says:
41 41  
21328-Display says:
11 31  
21353-Controller says:
30 30  
21409-Display says:
11 22 33 11 24 35 16 1A F0 11 8  
21953-Controller says:
1


In general the conversation between the display and controller is led by the display (display sends a message, controller responds). I have analysed the protocol, this is what I have found so far:

11 0A Amps
controller responds with two bytes, for example:
Code:
Display: 11 0A
Controller: 04 04
Display: 11 0A
Controller: 0C 0C
Display: 11 0A
Controller: 0F 0F
Display: 11 0A
Controller: 19 19

The two bytes are always identical. This byte, converted to decimal and divided by two, holds the Amp value. This value is the amount of amps the controller determined the motor should have, so it is not a measured value.

11 20 Wheel RPM
controller responds with three bytes, for example:
Code:
Display: 11 20
Controller: 0 53 73
Display: 11 20
Controller: 0 5C 7C
Display: 11 20
Controller: 0 63 83
Display: 11 20
Controller: 0 6E 8E
Display: 11 20
Controller: 0 7D 9D

The second byte (converted to decimal) holds the wheel RPM measured. The third byte seems to contain the same signal as the second byte, but with an offset of +32. The first byte is 0 when wheel RPM <= 256 and 1 when wheel RPM > 256. When wheel RPM > 256, the second byte starts from 0 again.

From these values, the speed (in km/h) of the bike can be calculated:
Code:
rpm * wheelsize_in_meters * 60 / 1000
([byte 2] + [byte 1] * 256) * wheelsize_in_meters * 60 / 1000)

The interval at which this message is sent is irregular (ranging from .8 to a couple of seconds). So if you want to use speed to determine distance traveled, you will need to know the time between the two samples.

11 31 Bycycle moving/stationary
controller responds with two bytes, for example:
Code:
Display: 11 31
Controller: 30 30
Display: 11 31
Controller: 31 31

The two response bytes are always identical and seem the indicate weather speed=0 (stationary) or speed >0 (moving). Value 30 indicates stationary and value 31 indicates moving. I assume that the display uses this information to calculate average speed while moving and to shut down after a certain time of inactivity.

16 1A F1 lights on
typical response from controller: 1
message is sent by the display periodically and when user turns lights on

16 1A F0 lights off
typical response from controller: 1
message is sent by the display periodically and when user turns lights off

11 08 UNKNOWN pedal activity?
controller responds with one byte, for example:
Code:
Display: 11 08
Controller: 1
Display: 11 08
Controller: 0

Controller response is 1 or 0. Not sure what this signal relates to, might be pedal activity:

11 08.JPG

11 11 Battery Percentage?
controller responds with two bytes, for example:
Code:
Display: 11 11
Controller: 64 64
Display: 11 11
Controller: 5E 5E
Display: 11 11
Controller: 5F 5F

The two response bytes are always identical and seem to hold 'battery percentage'. Converted to decimal, this value is never higher than 100 and decreases during a trip. This value also drops when a lot of amps are drawn by the motor and rises again when the motor is doing nothing (voltage sag). Because of this voltage sag, it generates quite a noisy signal:

battery.JPG

PAS levels 16 0B xx xx

PAS levels are sent by the display periodically (and when user changes PAS level of course). Controller does not send a response.
Code:
16 B 0 21  	PAS 0
16 B 1 22  	PAS 1
16 B B 2C  	PAS 2 (or PAS 1 for 5 PAS levels)
16 B C 2D  	PAS 3 (or PAS 1 for 3 PAS levels)
16 B D 2E  	PAS 4 (or PAS 2 for 5 PAS levels)
16 B 2 23  	PAS 5 (or PAS 2 for 3 PAS levels)
16 B 15 36  	PAS 6 (or PAS 3 for 5 PAS levels)
16 B 16 37  	PAS 7
16 B 17 38  	PAS 8 (or PAS 4 for 5 PAS levels)
16 B 3 24  	PAS 9 (or PAS 5 for 5 PAS levels) (or PAS 3 for 3 PAS levels)

UNKNOWN messages
11 21 32 unknown
no response from controller, often you see this message combined with other messages, e.g.
Display:11 22 33 11 24 35 11 21 32 11 08
Controller responds to the "11 08" message from the display

11 22 33 unknown
no response from controller, often you see this message combined with other messages, e.g.
Display:11 22 33 11 24 35 11 21 32 11 08
Controller responds to the "11 08" message from the display

11 24 35 unknown
no response from controller, often you see this message combined with other messages, e.g.
Display:11 22 33 11 24 35 11 21 32 11 08
Controller responds to the "11 08" message from the display

11 25 36 unknown
no response from controller, often you see this message combined with other messages, e.g.
Display: 11 25 36 11 08
Controller responds to the "11 08" message from the display


Typical start-up converstion:
Display says:
11 11
Controller says:
0
Display says:
11
Controller says:
0
Display says:
11
Controller says:
0 0
Display says:
11 90 11
Controller says:
90
Display says:
90
Controller says:
40 D0 90 40 D0

Not sure what information is exchanged there. I can imagine that it contains speed limit that is set in the display (the controller should have this info to limit the speed when set to 'Speed limit By display's command')

16 1F 02 9F D6 unknown
16 1F 2 55 8C unknown

I have set-up the Arduino to recognise 'known' messages and responses from the display/controller parse them and sent this information in a predefined format over bluetooth using the HC05 module. You can connect your phone to HC05 module and using the app Bluetooth Electronics (https://play.google.com/store/apps/details?id=com.keuwl.arduinobluetooth&hl=nl) you can visualise the current signals on your phone:

Screenshot_20180604-164453.png
 
Simple and ugly version=switch for throttle bypass and flick up the pas setting to 9 that you configured to be 50% power and 50% speed so you won't reach the legal limit even in different gearing (not enough power).
 
Wow, I missed this earlier. This is great information.

If enough of the protocol gets deciphered, it would be possible to make your own display unit with custom features, including the magnet switch.

Another nice feature would be to have an accurate battery meter for 52v packs.
 
fechter said:
If enough of the protocol gets deciphered, it would be possible to make your own display unit with custom features, including the magnet switch.

this is well underway at the french forum. ESP32 implementation.
 
MCC nice job! You must have spent hundreads of hours with this project.

So that chinese bafang protocol does seem quite dumb. But as it is simple it can be reversed engineered.

I have been developin a LCD display with actuall pas, gps and range back 3y ago when I bought my first bafang. If I would know it us so simple protocol, would calculate it just from voltage, current and speed info.

Now there us egg rider, nice lcd, but does not have remote buttons. I would like to have lcd in the center of handlebars.

Thanks for that bt software, did not know about it! Is it BT SPP protocol? But again just Android.

Now I am playing with Tiny BMS and modbus.
 
Well done MCC! Great work!

I have been thinking of doing something similar, but was originally planning to ditch the display altogether and just terminate the display cable directly into a bluetooth serial module and control through an android app (for analytics). But now I see that you can add the module in between the controller and display. Few questions:

1) How is it working out? Does the module "drop" any commands it is supposed to forward to display?

2) Have you done any further development? I would be interested in writing an Android app - would you care to share the interface from bluetooth to Android (or is it just BBS serial?)

Cheers!
 
Wow, this is exactly what I am looking for. Would this work on the Bafang Ultra 1000W? Or is it different somehow?
 
Brilliant! Thank you for sharing this with us!
 
Would it be possible to modify the firmware in the DPC-18 to be able to do this by holding one of the buttons?

If not, do you think this will work without modification on a Bafang Ultra? The display is the same I think. But the engine is different.

EDIT: Just got com0com and HTerm setup, and tried using this with the newer Bafang programming software (found here https://electricbike-blog.com/2017/11/23/i-void-warranties-hacking-the-bafang-ultra-max-mid-drive-ebike-drive/ )
And I cannot get it to connect, I get this error: Read The Controller Info CheckSum Error.
But there is no more information. I tried removing the ending 1B which is for checksum, but it doesn't work.
How do I find out what the correct checksum is?
 
I actually got some progress done. I managed to listen to the COM-port with a Serial Port Monitor application when I connected the bike with the Bafang Programming software. So I now have the correct response to be able to connect with com0com virtual ports.
The connection request from the programming software looks like this:
Code:
11 51 25 80 F6
This is the response sent from my bike:
Code:
51 10 53 5A 42 46 53 57 31 32 31 35 31 30 30 34 02 1E EE

Tried it with com0com and HTerm and I am able to connect and I have managed to capture the 25km/h settings too.

I have also confirmed that the Read pedal assist settings looks like this:
Code:
11 53

Which means I got a good chunk done already for the Bafang Ultra.
What I cannot figure out is how to get this command "Command that display sends when turn off button is pressed"?
And all the PAS commands?
Plus the DSpeed, DAmps, Dmoving, DBatPerc, Dunknwn, PASsuccessResp?
Do I need to figure out all these too? Or is it possible to strip the code of some of the functions. I don't really need the part that shows the graphs and percentage etc. only the cop mode button.
 
Really nice work. I hope to get my kit and play around. Maybe we can create a bafang specific cycle analyst.
 
Hey! Has anybody brought it to work? I'm stuck and initially planned to use it for my brakelight system. I'm getting "Error 30: Communication fault". Havn't changed the arduino code, beside skipping HC-05 and using "Micro-USB COM Serial", while using Arduinos Serial Monitor. Using an BBSHD, custom profile. My wiring looks like this:

Code:
D: Display
C: Controller

 
Tomblarom said:
Hey! Has anybody brought it to work? I'm stuck and initially planned to use it for my brakelight system. I'm getting "Error 30: Communication fault". Havn't changed the arduino code, beside skipping HC-05 and using "Micro-USB COM Serial", while using Arduinos Serial Monitor. Using an BBSHD, custom profile. My wiring looks like this:

Code:
D: Display
C: Controller

TwhqRkS.jpg

Have you tried to remove all logs from the serial port? During my experiments I had some trouble because I was introducing too much delay in the communication.
 
"Cop Button"??? Where do you live that cops don't have anything better to do and are hassling ebikers, so we can all avoid it?
 
I found the schematics quite confusing, since you can use colors and additional parts in Fritzing. Thanks to the help of @guancio, I finally was able to get the wiring working and made a new wiring diagram. In my case I used Serial1 and Serial2. Fritzing file in attachments.

I'm currently working on a solution to implement brakelight, control turn signals and read specific events between Controller / Display to control the lights.

While my testings I couldn't establish a connection to the controller without the resistors. People told, it's possible without any. I used this simple forward Arduino Sketch: https://forum.arduino.cc/index.php?topic=614872.0

Code:
  Serial.begin(115200);   //PC Serial Com Port
  Serial1.begin(1200);    //BBS02 controller    @RX18 TX19
  Serial2.begin(1200);    //Display DP-C18      @TX16 RX17

IAUgXoU.png


Edit: Swapped TX / RX of display
 

Attachments

  • cop_button_wiring_v2.zip
    215 KB · Views: 165
A couple of years ago I did a nearly identical effort with a different processor

Got to the point of changing the settings on-the-fly (actually easy, just a *ton* of effort) and sniffing enough to get a good start on changing PAS levels and receiving errors, etc. All settings accessible to a device with wifi and a browser - the browser interface work was more than half the effort...

I was very slow business-wise at the time and then got silly busy so it got pushed to the side... With the current state of world affairs my business ground to a screeching halt at the end of February and so I may be able to kick the dust off... I still have a lot of code and was prudent enough to document it well... may move on to a different processor at this point though...
 
NYC has over 2 million worth of confiscated eBikes warehoused, mostly from low-income immigrant delivery workers
 
I'm still testing and experiencing a noticeable delay between braking and display speed changes. It takes 2-3 seconds until the display shows the correct value. I made a few changes in the code, but it's occurring even with the original code. I was wondering what's causing this behavior? Tried to disable each and every Serial.print() towards the HC-05 module.

Edit: Experienced the same with pure C and it was because I never went for a ride. That delay is normal .
 
This is amazing work. Thank you so much for sharing this. I was told ASI controllers are compatible with Bafang display et al, and they have a published parameter dictionary complete with addresses... I'll be testing parts of your code and whatever I can read from the serial communications from BacDoor to the controller to see if i can write parameters on my BAC8000 with my Arduino (Geogram One lowjack) on my bike, so that I can create a switch to control field weakening. Perhaps I can change phase amps as well. Then I can have a switch to swap between a nice 'below saturation' efficiency tune and a "wheelie machine" tune with field weakening, then use the Cycle Analyst for other functions e.g. torque sensor ramp/power and max power.
 
Back
Top