building the BESC - 100V, 400A motor controller [NOT RECOMMENDED]

Status
Not open for further replies.

owhite

100 W
Joined
Aug 3, 2020
Messages
285
-------------------------------------------------------------------------
This post has been locked by request of the owner. -Moderation
-------------------------------------------------------------------------

CAUTION -- I DO NOT RECOMMEND USE OF THIS CONTROLLER.

What follows is a discussion of attempting to use an open source board that has not worked for a number of people. I've spoken to the mods who request we leave the thread here for instructional purposes but again, dont use this board.

-owhite

x0HzrMm.jpg


INTRODUCTION
This write up is an overview of building an open source electronic speed controller (ESC) that was originally posted here: BESC - beefed up VESC motor controller for high power ebikes

The designer of big(?) ESC (BESC) goes by user name galp. Galp is incredibly prolific based on his posts at his blog:

and he made at least two versions of the BESC:

and has some comments on that page about making a third version.

the specs for the BESC-G2 are:
  • 24S lipo/100V max supply voltage
  • 400A max phase current
  • 24x IRF135B203 135V MOSFETs

and he provided a write up of the G2 here:

The BESC is a variant of the Vedder ESC which originally was an open source ESC project which has been partially commercialized. Galp publicized development of the G2 over here on the VESC project site:

VESCs can be found in abundance on amazon, search under that name. The cool thing about VESCs is comes with a very nice interface

and if you have a functional VESC, there are some nice instructional videos on using the VESC-tool.

So, the following is a description of my experience of getting a BESC-G2 into my hands for testing. This is not a step by step tutorial, and the skill levels required are you at a minimum have to know about electronics, using Kicad printed circuit board (PCBs) software, ordering PCBs, and PCB assembly.

100v, 400A. I'm gonna assume you know what youre doing.

Galp stores his PCB files here:

PRINTED CIRCUIT BOARD ASSEMBLY BY EMAIL
I downloaded his files, loaded them into kicad, and checked all the parts to make sure that I could create a list from mouser.com, the electronics supplier. Kicad has a bunch of menu functions that help you with reviewing the parts list. My favorite is Tools-->edit symbol fields in Eeschema. Start with this list and painstakingly make sure that all of the parts are still available. Note this part: USBLC6-2SC6 was wrong. The correct one was USBLC6-2P6. Another problem which was his original 8MHz crystal was not obtainable, but I was able to use: ECS-80-18-30B-AGN-TR which fit on the footprint of his design exactly. If you want to try making your own board be prepared for the fact that parts always go out of date, and you either have to find a part that fits in the same footprint as before,

NOTE: as mentioned by Galp's build post (BESC-G2 – BESC) be sure to remove U7 and bridge the lines between pins.

At any rate, the size of some of the parts on the G2 are less than 1mm, and you may be more brave than I but assembly involve simply way too many parts. Instead I used a Printed Circuit Board Assembly (PCBA) service. Kicad makes lists of all the parts, and it creates a "centroid" file, which tells the factory how place all the parts in their correct location. Check out the export menu in pcbnew. Again this is not a tutorial so I wont go through all the steps but I loaded the comma separated values into excel to massage all the lists into the correct format. I then ordered the PCBA at PCBway.com. Approximate cost was $190 for PCB and the parts to make the complete board. (Note: this only reflects the cost of SMD parts, not the through-hole parts like the MOSFETs). I ordered 4 boards total, and it took about 4 weeks.

The boards arrived and look beautiful. Have a look at PCBway's videos on PCBA, they are kind of mind-blowing in terms of how complicated it is to put these boards together. The final product is amazing and I simply dont understand how they can make money from the process. Anyway. The first thing to test was power supply on the board. This involved soldering in the DC-to-DC converter (P/N R-78E5.0-0.5) as well as some of the 0.1 inch headers and applying a bench power supply to the power rails of the board. The 12 volt, 5 volt, and 3.2 volt systems on the board checked out. Then it came time to flash the firmware on the board. If you havent done this before search on "STM32 st-link programming" and you'll find about 2,000 tutorials. This part is nerve-wracking because at this point it is very hard to tell if you board is actually going to live.

FLASHING THE BESC FOR THE FIRST TIME
I also found a tutorial on simply making the LEDs flash with this STM32 MCU:

and I got that to work which was a big relief. That meant the components supporting the STM32F405RGT were working. Look I know I didnt design or assemble the thing but seeing it work for the first time was exhilarating just the same.

Ideally, it would be good to compile the firmware from scratch. There are some instructions as galp's code repository GitHub - galpavlin/BESC-G2: BESC second generation, BUT that did not end up working for me. The code that I flashed just didnt run. Galp left a copy of his firmware as a binary, and flashing that required installation of the st-flash utility on my mac to run. I connected the st-link2 hardware device to the G2, and used this on the command line:

Code:
$ st-flash write ../Downloads/besc-g2.bin 0x8000000
to load galp's binary. When I plugged in the USB connector, this resulted in these two ports appearing in my /dev directory:
/dev/cu.usbmodem3041
/dev/tty.usbmodem3041

That was also landmark because it shows the board is now connecting with the computer.

Next installment will be a discussion of using the VESC-tool for programming, as well as the wiring etc needed to connect the board to the motor.
 
Last edited by a moderator:
FLASHING FIRMWARE
The following is a short write up for flashing firmware to the BESC G2 from scratch. Things like firmware change all the time but this is a quick write up for flashing the firmware on the BESC circa December 2020. Basically, go to https://github.com/vedderb/bldc and read the instructions. Do NOT use the gcc-arm tool chain on a macOS; as of now it will compile your firmware and load, but the board will not behave properly.

The copies of code at the time of this build were:
bldc:
commit 7f4c6f41f0ad946f06bd151533538d798ede9245
Date: Tue Aug 18 12:20:09 2020

BESC-G2:
commit 84c037e712efe54421cdff30279646a578d49fdb
Date: Tue Aug 25 18:53:57 2020

This was done on the command line of a completely fresh install of ubuntu 18.04....
Code:
$ sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa
$ sudo apt update
$ sudo apt install gcc-arm-embedded
$ sudo apt install openocd
$ sudo apt install git
$ wget vedder.se/Temp/49-stlinkv2.rules
$ sudo mv 49-stlinkv2.rules /etc/udev/rules.d/
$ sudo udevadm trigger
$ git clone https://github.com/galpavlin/BESC-G2.git
$ git clone https://github.com/vedderb/bldc.git
$ cp -r bldc new_build
$ rm new_build/hwconf/hw_60*
$ cp BESC-G2/firmware/conf_general.h new_build/.
$ cp BESC-G2/firmware/hwconf/hw_bescg2.* new_build/hwconf/.
$ cd new_build/
Now we are basically patching the hw_bescg2.h file according to user gowrav's suggestion on this post:
https://vesc-project.com/comment/6298#comment-6298

You could also just do this with a manual edit to hwconf/hw_bescg2.h, or run:
Code:
$ cat hwconf/hw_bescg2.h | sed 's/define.*CURRENT_AMP_GAIN.*/define CURRENT_AMP_GAIN     20/' > tmp
$ echo "#define INVERTED_SHUNT_POLARITY" >> tmp
$ mv tmp hwconf/hw_bescg2.h

Now connect your st-link2 to the board and your ubuntu machine's USB and run:
Code:
$ make
$ make upload

Congratulations - if all goes well this installs a newly compiled version of firmware on to the BESC board. The board has a blue light to indicate it has power, and a green light which should be solid green, not blinking - and a red light to indicate problems. Hopefully the red light will not be blinking. By the way for the very ambitious when I had problems with the board I was able to use gdb, a debugger, which allowed me to put breakpoints in the code to find some error codes and add additional fixes to the code. Hopefully you will not need to do that.

There is a fantastic resource called VESC tool that helps with board programming. VESC tool is available at this repository:
https://github.com/vedderb/vesc_tool

And another person has made compiled versions of the tool for macOS and win 10.
https://github.com/rpasichnyk/vesc_tool/releases

The status of G2 testing - it is still underway and looks to be very promising.

NOTE CAREFULLY: Other readers have not been using the INVERT_SHUNT_POLARITY. This is probably because in some BESC board designs U7 has been eliminated.
 
That's some good news! Me and my friend have ordered few boards too. We will try to assemble them ourselves so we will probably not get to this stage anytime soon but it is good to hear good news from others :) Please keep us posted :)
 
CREATE A CUSTOM SERIAL DISPLAY
So hey - ever wanted to get serial data out of your motor controller and display it using an arduino? Since the BESC G2 is based on the VESC family, it's really simple. VESCs are very well supported, and among the things they can do is stream data in a couple formats, and in this case I tried using the serial.

I'm skipping over a couple of steps, because there are a lot of tutorials for using arduinos, and hooking up peripherals to them like an oled screen. I'm also going to skip over how you'd use some of the libraries used here in my arduino code - you'll have to figure out how to load code libraries in your favorite coding environment. Youre also going to need knowledge of wiring to arduino pins, and have general experience with serial communications.

But if that comes naturally to you, let's proceed.

A VESC contributor made a great set of utilities for Arduino connections via the serial. Download this:
https://github.com/SolidGeek/VescUart

The author has some example code for both reading, and sending signals to VESCs using an arduino. In my case I just took readings and wanted to display them.

Connections.
As already discussed the BESC circuit board is freely available, and can be found here:
https://github.com/galpavlin/BESC-G2

The BESC serial connector is labeled J14, and the pins are:
CTOhgyf.png


You'll need to make a connector between those pins and to the external serial pins of your arduino. In my case I use a teensy 3.2, so I connected the BESC pins to serial1, using pins 0 and 1 of the teensy.

This also assumes you have configured your BESC board already using VESC-tool. If there are any takers out there I will do my best to document that at some time -- and of course the problem is that in a year from now the VESC-tool may be different enough that my documentation will be out of date. So first, set up your board, and use the icons on the right most side to save your motor configuration to the BESC board AND you have saved your app configuration to the BESC board. If you have not configured these, proceeding may cause problems.

But after you've programmed your BESC, to get it to send data to the serial, plug in the USB to your BESC...
Start VESC-tool
Go Connection, USB-Serial tab, select your port, click the "Connect" button
The VESC-tool should indicate it's connected.

Then, do these steps:
Go to App Settings --> general
select ADC and UART

Go to App Settings --> UART
change baud rate to 19200

Then save results. Same story, go to the icons on the right most side to save your motor configuration to the BESC board, and also save your app configuration to the BESC board. Disconnect VESC-tool, youre now ready to try connection to the arduino.

Arduino code
You could try the program:
https://github.com/SolidGeek/VescUart/tree/master/examples/getVescValues
and then just look at the results in the terminal program of the arduino IDE.

In my case I had an old joystick printed circuit board laying around. The circuit diagram for my joystick is here:
https://i.imgur.com/qG0zxSb.png

This has an oled and I made a connector to it which is not visible because it's on the underside of that board. I connected it up to BESC, and loaded this code on to the teensy:
Code:
#include <VescUart.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define LED_PIN    13

Adafruit_SSD1306 display(4);

uint8_t btnPin1 = 9;
uint8_t btnPin2 = 8;
uint8_t btnPin3 = 7;
uint8_t btnPin4 = 6;
uint8_t joy1Pin  = 17;
uint8_t joy2Pin  = 16;
uint16_t joy1PinVal; uint16_t joy2PinVal;

byte hiByte;
byte loByte;

uint8_t btn1; uint8_t btn2; uint8_t btn3; uint8_t btn4; 

VescUart UART;

void setup() {

  Serial.begin(9600);
  Serial1.begin(19200);

  pinMode(LED_PIN, OUTPUT); 
  digitalWrite(LED_PIN, HIGH);

  // while (!Serial) {;}

  UART.setSerialPort(&Serial1);

  pinMode(btnPin1, INPUT);
  pinMode(btnPin2, INPUT);
  pinMode(btnPin3, INPUT);
  pinMode(btnPin4, INPUT);
  pinMode(LED_PIN, OUTPUT);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  
  display.setTextColor(WHITE);

}

void loop() {
  
  if ( UART.getVescValues() ) {
    Serial.println(UART.data.rpm);
    Serial.println(UART.data.inpVoltage);
    Serial.println(UART.data.ampHours);
    Serial.println(UART.data.tachometerAbs);

  }
  else
  {
    Serial.println("Failed to get data!");
  }

  joy1PinVal = analogRead(joy1Pin); joy2PinVal = analogRead(joy2Pin);
  btn1 = digitalRead(btnPin1);  btn2 = digitalRead(btnPin2);
  btn3 = digitalRead(btnPin3);  btn4 = digitalRead(btnPin4);

  display.clearDisplay();
  display.setTextSize(1); 
  display.setCursor(0 ,5); display.print("Volts:");
  display.setCursor(50,5); display.print(UART.data.inpVoltage);

  display.setCursor(0 ,15); display.print("RPM:");
  display.setCursor(50,15); display.print(UART.data.rpm);

  display.setCursor(0 ,25); display.print("Ah:");
  display.setCursor(50,25); display.print(UART.data.ampHours);

  display.setCursor(0 ,35); display.print("Tach:");
  display.setCursor(50,35); display.print(UART.data.tachometerAbs);

  display.setCursor(60,50); 

  if ( 1 ) { // use some timer
    display.setTextSize(2); display.print('*');
  }
  else {
    display.setTextSize(2); display.print('.');
  }
  display.display();

  delay(200);
}
Boom, real time reporting of data from the BESC:
iTNc1sM.jpg


I'm starting to like this BESC.
 
There are a couple issues. I can imagine the firmware will work pretty well as it is now. But I'd feel better about you trying it if there were other people using this thing that could help you if you need it - the forum over on VESC-projects is active for the VESCs, but I was not able to get much help with the BESC. VESCs and the BESC use the same firmware but help from someone that knows the BESC G2 would be good. I am happy to help, but the firmware is pretty sophisticated and I just have moderate skill. :?

Another issue. Do you have much experience debugging circuits? I had mine assembled at a PCB house - it worked very well but there can always be some kind of issue that could come up and it would be good if have some electronics experience.

Finally, you still need to be able to use the VESC tool to configure the board. I'm running mine with a QS205 motor and I need to try to write up how I did it - but if you arent using that motor then some of the configuration will probably be different.

I have no information about version 3.
 
Thanks for the reply.
I will be following your experiences and see how it turns out :wink:

I would like to use MTPA that should be available for VESC, and then I guess for BESC too.
But if it is not the same firmware, maybe not?
I am interested to use it on a qs 138 70 or 90h, IPM motor.

I dont have much experience in electronic circuits, but I can use multimeters and oscilloscope.
I can find bad mosfets and things like that, bad soldering.
My thought was to try to get boards with the small parts populated, and just have to solder the big parts.

I will take a look at VESC tool, I have been looking around on the sight before. But I dont remember why I dont seem to have downloaded it.
 
Well first of all I have to say, that this is the best thread so far I've found with regards to build a 10KW+ Foc Motor Controller.
Thank you very much for this. I'm trying to build a hybrid conversion into my car but first of all I have to get the motor spinning.
Unfortunately my time is also limited due to my mechanical engineering studies and my side work. But somehow it will work out. I'm already a year into this project. Keep me posted on your experiences! Next thing is the blog of pavlin which in going to read.

Regards from Germany

Moritz
 
Thank you for the thread owhite, I have also built BESC-G2 (FW http://pavlin.si/besc/wp-content/uploads/2020/08/BESC_G2_29.8.2020.zip) it is working in BLDC mode but not in FOC mode,
trying to detect and get RL parameter the motor is not spinning at all , did you test in FOC mode?
 
The places of R66 and C66 at the github site are right but the labels on my pcb were switched and I switched them according to the labels which was wrong, so switching them again back solve the problem in FOC mode.Now everything is ok.
I will follow galp instructions to solder bus bars with hot air gun.
By the way I'm waiting from aliexpress seramic insulations
https://www.aliexpress.com/item/4000063252698.html?spm=a2g0s.9042311.0.0.20dd4c4dzeAfxa
and heatsinks
https://www.aliexpress.com/item/32839535081.html?spm=a2g0s.9042311.0.0.20dd4c4dzeAfxa
 
well congratulations on getting your board together!

I'm not really not sure there is anyway to prevent some kind of warping of the board if you use heat to attach a rod as the bus bar. Here is my thinking. No matter what you cant keep the copper rod from lengthening when you heat it. What then happens is that it longer than it will be once it is cooled, and then the board will warp. The one possibility is if you possibly tack a small length of the bar down with solder, allow it to cool completely, and then tack down another part of the rod. I tried to do this, and it's still warping the board.

The other problem will be is that the once the board heats up, it is guaranteed to lengthen and heat up the board.

I'm now starting to understand why there are PCB bus bars made of copper braid:

https://i.imgur.com/q3cAUBD.jpg or
https://i.imgur.com/gPefdow.jpg

I'm designing a new version of the board that might help, and will post pictures soon.

MANAGING THE HEAT SINKS
Until then, I was waiting to post this until I was further along but since youre working on the heat sinks I thought I'd show these pics. The issue is it is nearly impossible to get the heat sinks that are along the middle of the board attached to a plate. So what i thought I would try is shown here.

This plate was cut by waterjet and then I dug out a pocket and outer grooves using a router:
https://i.imgur.com/swNl9Ur.jpg

The purpose of the pocket is so a smaller aluminum plate will get attached to the MOSFETS with heatsink tape:
jb5ERJX.jpg


The source of the heat sink tape is here:
https://www.mouser.com/ProductDetail/951-HF300P0450010512/

The MOSFET with heat sink tape will get attached to the smaller plate using insulating washers. This shows just one MOSTFET:
4rx9mRv.jpg


But eventually the small plate will be attached to the MOSFETS on the BESC:
GOWryiO.jpg


And then the larger plate will get bolted on to the smaller one. You can see how the pocket in the larger heatsink will drop on top of smaller plate. Thermal grease will be used for everything, as well as locktite on all the bolts when I put the final version together.
4DrE7cR.jpg


Notice the 10 gauge wire that was soldered to the BESC in that picture. Again, I dont think this is the way to go. I'd like to try a different approach for the bus bar.

finished assembly with messy thermal grease:
https://i.imgur.com/LahqeBf.jpg

view from the bottom of large plate:
https://i.imgur.com/c2kFwP0.jpg
 
MOTOR TEMPERATURE READING ISSUES
Thermistors are based on a voltage divider, and there does not seem to be one in Galp's circuit. It may be that he did this so his board would have the ability to deal with thermistors that have different resistances. Most VESCs assume the motor will be 10k, and in my case my motor uses a KTY83/122 which is 980ohms. Note, the spec sheet for the KTY83/122 does not refer to it as a thermistor, but it does change in resistance as the temperature goes up. Not sure what to say about that.

In order to do testing on the board, first I compiled a version of the firmware to make a much higher temperature range for the motor:
Code:
#define MCCONF_L_LIM_TEMP_MOTOR_START	80000.0
#define MCCONF_L_LIM_TEMP_MOTOR_END	800010.0

then I found out you can go to Developer-->Parameter Editor Mcconf and then change the values for I_temp_motor_start, I_temp_motor_end by hand. I didnt try it but I assume it works.

So using the BESC board as is, I get readings that float all over a range of 410-450 Celsius when I look at real time data on VESC-tool.

I added in a 1k resistor divider which may not be ideal because the thermistor is 980 ohms. When the 1k resistor is tied high to 3.3v the temp reading on VESC-tool reads at very reasonable levels that are within 1 degree Celsius with the board measurement.

I am modifying the next version of the board to allow the user to simply add a through-hole resistor to match their thermistor.
 
USING THE VESC-TOOL
The following is a description of using VESC-tool to get the motor spinning. These steps are probably very close to a step by step set of instructions, but as usual should be treated as a guide because your situation may be different. VESC-tool is great software with lots of great functionality but it took me a while to learn. VESC tool is available at this repository:
https://github.com/vedderb/vesc_tool

And another person has made compiled versions of the tool for macOS and win 10.
https://github.com/rpasichnyk/vesc_tool/releases

To begin, connect motor, hall sensors, connect USB cable, power supply to board, power up the board. There is a panel on the left of the main VESC-tool screen. Use connection, find the board, and hit connect button. Then go to Motor settings --> FOC --> General which creates a panel.

On this panel select "Hall Sensors" from Sensor Mode. On the same panel there are buttons on the lower part of the panel. Hit "RL" and the Lambda symbol. These commands make the motor squeal and also turn. If those steps work it does that's a good sign. If it does, close that panel. Over on the right most VESC-tool column there are buttons. Do mouse overs on those buttons. Look for "write motor configuration", select that button. Look for "Write app configuration", select that button.

Now, left most panel, hit "Welcome & Wizards". Select "Setup Motors FOC", in my case I select "E-bike DD hub motor". Dont panic because of the scary sounding warning message. My power supply is running at 48v right now, so for "Battery Cells Series" I selected 10. For Battery capacity, just to make the thing turn, I set 6 amps. This is just to start tuning my motor and I assume these settings should be changed later on. Then hit next, pick your gear ratio (I use direct drive) and then hit Run detection.

If all goes well that spins the motor. There were several times in the past when it would throw a fault error because of the thermistor (discussed above) or a connection problem. If the VESC throws an error on the leftmost panel you can select "VESC Terminal". Use this to get some debugging information. For example if your red LED is flashing and you type "Faults" it reports the latest errors. But if everything seems to be working, and you get past the calibration wizard, save your results using "write motor configuration", and "Write app configuration". Note, that "saving" means the parameters for the motor, and the app, are stored in the BESC's memory. If you want to save your parameters to the computer as files, look for File --> Save app configuration XML and File --> Save motor configuration XML.

Good luck.
 
owhite said:
Notice the 10 gauge wire that was soldered to the BESC in that picture. Again, I dont think this is the way to go. I'd like to try a different approach for the bus bar.

Hello owhite!
I used a 4x4mm copper rod for my second board. Soldered in pizza oven. There is still some bending of the board, but that can be reduced if the board is screwed down while soldering (did this on the first pcb with 3 copper wires).
Regards, julwaech from vesc forum

PS: really nice thread :bigthumb: :bolt:

VkyTVPQ.jpg


WcC9mIn.jpg
 
Looking good so far! I believe based on the cross section size you should be able to handle the amperage to run at least three houses at the same time. :)

So it looks like you have not soldered on some of the SMD parts yet? Will you be adding them by hand?

Also, be mindful that there are some through-holes for the caps that go between the bus bars. At some point you'll need to get a soldering iron in there. Ideally it will be a soldering iron with higher wattage because the all the copper tends to draw away the heat from the solder pad very quickly.
 
owhite said:
So it looks like you have not soldered on some of the SMD parts yet? Will you be adding them by hand?

Also, be mindful that there are some through-holes for the caps that go between the bus bars. At some point you'll need to get a soldering iron in there. Ideally it will be a soldering iron with higher wattage because the all the copper tends to draw away the heat from the solder pad very quickly.

Yes, but that pcb was more of test. I mainly used a small hot air gun for soldering near the copper, although if done too often the pcb will take damage. I assembled the whole board by hand. I would not do this again, just not worth the time :roll:
 
REQUEST FOR PCB CHANGES ??
I am taking suggestions for minor improvements to BESC G2 board. I'm making modifications in kicad and will eventually post the new files if anyone is interested. So far what I have done is:

Removed U7.

Disconnected lower right mechanical hole from GND - others may not agree this seemed to be dangerous to me in that a bolt going to a chassis could make the whole chassis live. Grounding the board should be easy enough if the user wants to do that by hand.

Added solderable through holes to allow the user to put in resistor to serve as a divider that matches the motor's thermistor.

Moved some mechanical holes. To me it seemed like there's too many. The whole board gets mounted to a heatsink using the MOSFETs, that is plenty of ridged mechanical mounting that will attach the PCB to a lower plate. While other people may do something differently, in my configuration the lower plate will then be used to attach an outer housing.

Created clearance between some caps and busbar. In the current design there's about 1mm between solder pads for some of the caps and the bus bar, making it very easy to create a short between Vcc and GND - so I increased that gap. The result was to make the board 1mm wider, which seemed reasonable.

Cosmetic changes. I added a couple things to make the thing a little prettier. https://i.imgur.com/1qjY9RI.png

Added through hole solder points for the busbars. People may not like this idea, but I would like to explore a different strategy of attaching the busbar that does not require heating the busbar as much. I'm still thinking about it, and will post my results when the time comes. My next PCBA of this board is probably a few months away.

Note: for any files that I release they will always include attribution to Galp on both the PCB, as well as any repo that host's my files.

SUGGESTIONS FROM ANYONE ARE WELCOME for additional changes to the circuit and the board.
 
A CUSTOM SERIAL DISPLAY
An update on the display system for the BESC. I got a 3.5inch TFT touchscreen that is 320x480 pixels from adafruit:
https://www.adafruit.com/product/2050

They have a lot of documentation, and in combination with a teensy 3.2:
https://www.pjrc.com/store/teensy32.html

which is essentially a much faster arduino, I made the following circuit diagram:
https://i.imgur.com/TZ2TbgQ.png

and PCB layout:
https://i.imgur.com/5AYJPdi.png

This was then sent to:
https://www.pcbway.com/
to generate a circuit board. Kicad files are available to anyone that wants them.

The assembled system is pretty uninteresting to look at from the back:
https://i.imgur.com/G1s7jVF.jpg

But using the libraries from adafruit, and other code from online, I was able to put together this touchscreen display that reads data directly from the BESC and will eventually be slapped on to my bike:
4M139x9.jpg


The BESC reports temp values from both the motor and it has a thermistor near the MOSFETs on the board which are on the display. The code in case youre interested...
Code:
#include <VescUart.h>
// data that comes in from VescUart
// data.tempFET
// data.tempMotor
// data.avgMotorCurrent
// data.avgInputCurrent
// data.dutyCycleNow
// data.rpm
// data.inpVoltage
// data.ampHours
// data.ampHoursCharged
// data.tachometer
// data.tachometerAbs

#include <Ewma.h>
#include <SPI.h>
#include <Wire.h>
#include <ILI9341_t3.h>
#include <TouchScreen.h>

#define BRIGHTNESS_PIN  3
#define BTN_PIN         6

// Touchscreen pins
#define YP 20  // Y+
#define XM 21  // X-
#define YM 19  // Y-
#define XP 22  // X+

// Meter colour schemes
#define RED2RED 0
#define GREEN2GREEN 1
#define BLUE2BLUE 2
#define BLUE2RED 3
#define GREEN2RED 4
#define RED2GREEN 5

// This is calibration data for the raw touch data to the screen coordinates
#define TS_MINX 150
#define TS_MINY 120
#define TS_MAXX 920
#define TS_MAXY 940

#define MINPRESSURE 10
#define MAXPRESSURE 1000

// filter system
Ewma adcFilter0(0.1);
Ewma adcFilter1(0.1);
Ewma adcFilter2(0.1);
Ewma adcFilter3(0.1);
Ewma adcFilter4(0.1);
Ewma adcFilter5(0.1);

// Initiate VescUart class //
VescUart UART;

// For better pressure precision, we need to know the resistance
// between X+ and X- Use any multimeter to read it
// For the one we're using, its 300 ohms across the X plate
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

#define TFT_CS 10
#define TFT_DC  9
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);

int maxRoadSpeed = 32;

#define FRAME_W 320
#define FRAME_H 480

uint32_t runTime = -99999;       // time for next update

int reading = 0; // Value to be displayed
int displayInc = 0;
bool uartIsSnoozing = true;
int counterMax = 20;
int counter = counterMax - 1;

double dataValue[7]    = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
double oldDataValue[7] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0};

unsigned long keyPrevMillis = 0;
const unsigned long keySampleIntervalMs = 25;
byte longKeyPressCountMax = 80;    // 80 * 25 = 2000 ms
byte mediumKeyPressCountMin = 20;    // 20 * 25 = 500 ms
byte KeyPressCount = 0;

byte prevKeyState = HIGH;         // button is active low

void setup(void) {
  Serial.begin(9600);
  Serial1.begin(19200);
  UART.setSerialPort(&Serial1);

  tft.begin();

  tft.fillScreen(ILI9341_BLACK);
  tft.setRotation(0); 

  pinMode(BRIGHTNESS_PIN, OUTPUT); 
  pinMode(BTN_PIN, INPUT); 

  analogWrite(BRIGHTNESS_PIN, 240);
  clearFrame();
  drawWidgetFrame();
}

void loop() {
  if (millis() - runTime >= 100) {
    runTime = millis();

    uartIsSnoozing = true;
    if ( UART.getVescValues() ) {
      uartIsSnoozing = false;
      dataValue[0] = adcFilter0.filter(UART.data.inpVoltage);
      dataValue[1] = adcFilter1.filter(UART.data.tempFET);
      dataValue[2] = adcFilter2.filter(UART.data.tempMotor);
      dataValue[3] = adcFilter3.filter(UART.data.rpm);
      dataValue[4] = adcFilter4.filter(UART.data.avgInputCurrent);
      dataValue[6] = adcFilter5.filter(UART.data.ampHours);
    }
    
    reading += 17; if (reading >= 500) reading = 0;
    int ypos = drawWidgetFrame(); // only used for temps and wigets
    int xLeft = 10;
    int xMid = FRAME_W /3 + 20;
    int xRight = FRAME_W * 2 /3 + 20;

    if (uartIsSnoozing) {
      drawCompaint(ypos);
    }
    else {
      if (refreshValue(1) || refreshValue(2)) { drawTemps(ypos); }
    }

    switch (displayInc) {
    case 0:
      bigGauge((!uartIsSnoozing) ? UART.data.inpVoltage : 0.0, 0, 104, "VOLTS", 0);
      // note we should be using refreshValue but using counter to deal with static numbers
      if (counter == counterMax) {textWidget(xLeft, ypos, "aH", 91.0);}
      if (counter == counterMax) {textWidget(xMid, ypos, "AMPS", 0.0);}
      if (counter == counterMax) {textWidget(xRight, ypos, "MPH", 32.0);}
      break;
    case 1:
      bigGauge(reading, 0, 500, "AMPS", 0);
      if (counter == counterMax) {textWidget(xLeft, ypos, "aH", 91.0);}
      if (refreshValue(0)) { textWidget(xMid, ypos, "VOLTS", dataValue[0]); } 
      if (counter == counterMax) {textWidget(xRight, ypos, "MPH", 32.0);}
      break;
    case 2:
      bigGauge(reading, 0, maxRoadSpeed, "MPH", 0);
      if (counter == counterMax) {textWidget(xLeft, ypos, "aH", 91.0);}
      if (refreshValue(0)) textWidget(xMid, ypos, "VOLTS", dataValue[0]);
      if (counter == counterMax) {textWidget(xRight, ypos, "AMPS", 32.0);}
      break;
    case 3:
      bigGauge(reading, 0, 160, "aH", 0);
      if (counter == counterMax) {textWidget(xLeft, ypos, "AMPS", 91.0);}
      if (refreshValue(0)) textWidget(xMid, ypos, "VOLTS", dataValue[0]);
      if (counter == counterMax) {textWidget(xRight, ypos, "MPH", 32.0);}
      break;
    default:
      // statements
      break;
    }
    counter++;
    if(counter > counterMax) {counter = 0;}
  }

  // pushbutton management section
  if (millis() - keyPrevMillis >= keySampleIntervalMs) {
    keyPrevMillis = millis();
       
    byte currKeyState = digitalRead(BTN_PIN);
    currKeyState = !currKeyState;
       
    if ((prevKeyState == LOW) && (currKeyState == HIGH)) {
      keyPress();
    }
    else if ((prevKeyState == HIGH) && (currKeyState == LOW)) {
      keyRelease();
    }
    else if (currKeyState == HIGH) {
      KeyPressCount++;
      if (KeyPressCount >= longKeyPressCountMax) {
        longKeyPress();
      }
    }
       
    prevKeyState = currKeyState;
  }

  TSPoint p = ts.getPoint(); 
  if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {   

    delay(40);
    clearFrame();
    drawWidgetFrame();

    displayInc += 1;
    if (displayInc > 3) {displayInc = 0;}
  }  
}

// update periodically, or when there is a significant change in value
bool refreshValue(int p) {
  double x = oldDataValue[p];
  double y = abs(dataValue[p]-x);
  oldDataValue[p] = dataValue[p];

  if (counter == counterMax) {return(true);}
  return ((abs(dataValue[p]-x) < 0.09) ? false : true);
}

void shortKeyPress() {
  clearFrame();
  counter = counterMax - 1;

  Serial.println("short");

  displayInc += 1;
  if (displayInc > 3) {displayInc = 0;}
}

// not used for anything but keeping around
void mediumKeyPress() { }

// called when button is kept pressed for 2 seconds or more
void longKeyPress() {
  Serial.println("long");

  clearFrame();
  if (maxRoadSpeed == 32) {maxRoadSpeed = 60;}
  else {maxRoadSpeed = 32;}
  delay(1000);
}

// called when key goes from not pressed to pressed
void keyPress() {
  Serial.println("key press");
  KeyPressCount = 0;
}


// called when key goes from pressed to not pressed
void keyRelease() {
  Serial.println("key release");
   
  if (KeyPressCount < longKeyPressCountMax && KeyPressCount >= mediumKeyPressCountMin) {
    mediumKeyPress();
  }
  else {
    if (KeyPressCount < mediumKeyPressCountMin) {
      shortKeyPress();
    }
  }
}

void drawWord(int h, char *word) {
  tft.setTextSize(1);
  tft.fillRect(0, h-50, FRAME_W, 20, ILI9341_BLACK);
  tft.setCursor(0, h-50);
  tft.println(word);
}

void drawTemps(int h) {
  tft.setTextSize(2);
  // FET
  tft.setCursor(0, h-50);
  tft.println("MF:");
  tft.fillRect(50, h-50, 70, 20, ILI9341_BLACK);
  tft.setCursor(50, h-50);
  tft.println(dataValue[1]);

  // MOTOR
  tft.setCursor(150, h-50);
  tft.println("MO:");
  tft.fillRect(200, h-50, 70, 20, ILI9341_BLACK);
  tft.setCursor(200, h-50);
  tft.println(dataValue[2]);
}

void drawCompaint(int h) {
  tft.setTextSize(2);
  tft.fillRect(0, h-50, FRAME_W, 20, ILI9341_BLACK);
  tft.setCursor(0, h-50);
  tft.println("No serial");
}

void textWidget(int x, int y, char *units, double value) {
  tft.setCursor(x, y);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(2);
  tft.println(units);
  tft.fillRect(x, y + 40, 80, 15, ILI9341_BLACK);
  tft.setCursor(x, y + 40);
  tft.println(value);
}

void bigGauge(int value, int vmin, int vmax, char *units, int pos) {
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(4);
  tft.setCursor(0,0);
  tft.println(units);

  tft.setTextSize(3);
  tft.setCursor(180,0);
  tft.println("Max:");
  tft.setCursor(250,0);
  tft.println(vmax);

  int xpos = 5, ypos = 40, radius = 150;

  ringMeter(value, vmin, vmax, xpos, ypos, radius, "", GREEN2RED); 
}

int drawWidgetFrame() {
  int y = FRAME_H * 3 / 4;

  // draw the frames around bottom displays
  tft.fillRect(0, y, FRAME_W, 10, ILI9341_WHITE);
  tft.fillRect(FRAME_W /3, y, 10, FRAME_H / 4, ILI9341_WHITE);
  tft.fillRect(FRAME_W * 2/3, y, 10, FRAME_H / 4, ILI9341_WHITE);

  return(y + 20);
}

int ringMeter(int value, int vmin, int vmax, int x, int y, int r, char *units, byte scheme) {
  // Minimum value of r is about 52
  x += r; y += r;   // Calculate coords of centre of ring
  int w = r * .4;    // Width of outer ring is 1/4 of radius
  int angle = 150;  // Half the sweep angle of meter (300 degrees)
  int text_colour = 0; // To hold the text colour
  int v = map(value, vmin, vmax, -angle, angle); // Map the value to an angle v
  byte seg = 5; // Segments are 5 degrees wide = 60 segments for 300 degrees
  byte inc = 10; // Draw segments every 5 degrees, increase to 10 for segmented ring

  // Draw colour blocks every inc degrees
  for (int i = -angle; i < angle; i += inc) {
    // Choose colour from scheme
    int colour = 0;
    switch (scheme) {
    case 0: colour = ILI9341_RED; break; // Fixed colour
    case 1: colour = ILI9341_GREEN; break; // Fixed colour
    case 2: colour = ILI9341_BLUE; break; // Fixed colour
    case 3: colour = rainbow(map(i, -angle, angle, 0, 127)); break; // Full spectrum blue to red
    case 4: colour = rainbow(map(i, -angle, angle, 63, 127)); break; // Green to red (high temperature etc)
    case 5: colour = rainbow(map(i, -angle, angle, 127, 63)); break; // Red to green (low battery etc)
    default: colour = ILI9341_BLUE; break; // Fixed colour
    }

    // Calculate pair of coordinates for segment start
    float sx = cos((i - 90) * 0.0174532925);
    float sy = sin((i - 90) * 0.0174532925);
    uint16_t x0 = sx * (r - w) + x;
    uint16_t y0 = sy * (r - w) + y;
    uint16_t x1 = sx * r + x;
    uint16_t y1 = sy * r + y;

    // Calculate pair of coordinates for segment end
    float sx2 = cos((i + seg - 90) * 0.0174532925);
    float sy2 = sin((i + seg - 90) * 0.0174532925);
    int x2 = sx2 * (r - w) + x;
    int y2 = sy2 * (r - w) + y;
    int x3 = sx2 * r + x;
    int y3 = sy2 * r + y;

    if (i < v) { // Fill in coloured segments with 2 triangles
      tft.fillTriangle(x0, y0, x1, y1, x2, y2, colour);
      tft.fillTriangle(x1, y1, x2, y2, x3, y3, colour);
      text_colour = colour; // Save the last colour drawn
    }
    else // Fill in blank segments
      {
	tft.fillTriangle(x0, y0, x1, y1, x2, y2, ILI9341_DARKGREY);
	tft.fillTriangle(x1, y1, x2, y2, x3, y3, ILI9341_DARKGREY);
      }
  }

  // Convert value to a string
  char buf[10];
  byte len = 4; if (value > 999) len = 5;
  dtostrf(value, len, 0, buf);

  // Set the text colour to default
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);

  // Uncomment next line to set the text colour to the last segment value!
  tft.setTextColor(text_colour, ILI9341_BLACK);
  // Print value, if the meter is large then use big font 6, othewise use 4
  if (r > 84) {
    tft.setCursor(x - 80, y - 20);
    tft.setTextSize(6);
    tft.println(buf);
    // tft.drawCentreString(buf, x - 5, y - 20, 6); // Value in middle
  }
  else {
    tft.setCursor(x - 80, y - 20);
    tft.setTextSize(4);
    tft.println(units);
    // else tft.drawCentreString(buf, x - 5, y - 20, 4); // Value in middle
  }

  // Print units, if the meter is large then use big font 4, othewise use 2
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  if (r > 84) {
    tft.setCursor(x - 40, y + 30);
    tft.setTextSize(4);
    tft.println(units);
    // tft.drawCentreString(units, x, y + 30, 4); // Units display
  }
  else {
    tft.setCursor(x - 20, y + 5);
    tft.setTextSize(2);
    tft.println(units);
    // tft.drawCentreString(units, x, y + 5, 2); // Units display
  }

  // Calculate and return right hand side x coordinate
  return x + r;
}

// Return a value in range -1 to +1 for a given phase angle in degrees
float sineWave(int phase) {
  return sin(phase * 0.0174532925);
}

// Return 16 bit rainbow colour
unsigned int rainbow(byte value) {  // Value is expected to be in range 0-127
  byte red = 0; // Red is the top 5 bits of a 16 bit colour value
  byte green = 0;// Green is the middle 6 bits
  byte blue = 0; // Blue is the bottom 5 bits

  byte quadrant = value / 32;

  if (quadrant == 0) {
    blue = 31;
    green = 2 * (value % 32);
    red = 0;
  }
  if (quadrant == 1) {
    blue = 31 - (value % 32);
    green = 63;
    red = 0;
  }
  if (quadrant == 2) {
    blue = 0;
    green = 63;
    red = value % 32;
  }
  if (quadrant == 3) {
    blue = 0;
    green = 63 - 2 * (value % 32);
    red = 31;
  }
  return (red << 11) + (green << 5) + blue;
}

void clearFrame() {
  tft.fillRect(0, 0, FRAME_W, FRAME_H, ILI9341_BLACK);
}
 
Status
Not open for further replies.
Back
Top