Projects | My One Wheel Board
THEME: LIGHT
OneWheelBoard

My One Wheel Board

Last Updated: 2020-11-10

Project Overview

I'm not a skateboarder, but I have decent balance and can lay down code. So instead of learning to ride someone else's board, I built my own.

I wanted to be able to adjust my ride on the fly without having to pair up my phone, so I added a simple settings interface and utilized the existing switches.

My gryo filter uses a simple principle for smooth and reliable angle measurement. As the accelerometer jumps around quite a bit while in motion, I factored it's parameter depending how far off it was from the current gyro reading. The further the accelerometer was from the gyro, the less I mixed into the current angle routine. I then averaged the total output over the last ten readings with a bias closer to zero.

The drive routine is simple. Multiply power by current angle, then compound additional power when desired angle is reached. This helps to keep the board within the desired operational angle.

The overdrive works by adding additional power over time while the desired angle is maintained. This allows the board to continue to reach higher speeds while maintaining a comfortable ride angle.

My board is utilizing a 48 volt, 800 watt hub motor and a VESC for the drive train. Powered by a 12s2p Samsung 18650 25R battery pack, I get a little over 10kms of range, a comfortable cruising speed of 20km/h, and a top speed of just over 35km/h.

The controller is an Arduino Nano V3 paired with an MPU6050.

Total firmware size is 15082 bytes (49%) of program storage & 1154 bytes (56%) of dynamic memory. Eeprom storage is 28 bytes.

The board is 30 inches long and 9.5 inches wide. The aluminum railes are 1 inch wide, 2 inches tall, 1/8 inch wall thickness and 28 inches long with a 45 degree cut on each end.

Operating the Board

Safety First

Wrap yourself in a thick mattress before stepping on this board...

But seriously, wear a helmet and a well fitting pair of flat souled shoes.

Gloves, elbow and knee pads are also a smart idea.

Powering Up The Board

On power up, you must first wait for the counter to reach 0 and display the battery voltage. This step is the software calibrating the gyro offset.

The board must be in a still position for this to take place. If the board senses any movement, the counter will reset to 10 and restart the calibration. This process takes place everytime you power on the board.

Arming the Board

To arm the board, you must first place the board at a greater than 10 degree angle with the back on the ground.

You then must step on the back button or foot sensor, followed by the front button or foot sensor. The display will now show 1111 and then quickly to 0. To engage the motor, you must tilt the board to the level position.

The board will now be armed and in operation.

You must be standing on atleast one of the two buttons or foot sensors to maintain operation of the board. Due to the wait of the board, the breaks will immediately engage when both buttons are released to prevent the board from being a powered projectile.

Settings Menu

To enter the settings menu, hold down the front button or foot sensor for atleast 5 seconds then release. The rear button or foot sensor must not be pressed for this procedure.

To cycle through the menu, press and release the front button or foot sensor.

To alter a setting, press and release the back button or foot sensor.

To reset a value to its lowest setting, hold the back button or foot sensor for atleast 3 seconds and release.

To exit the menu and save your settings, hold down the front button or foot sensor again for atleast 5 seconds. Your settings are now saved and you will be shown the voltage displays.

Setting #1 (Way Mode)

This setting determines whether the board move only forward or in both directions.

1001 = 1 way. The board will only move in the forward direction.
1002 = 2 way. The board will move in both directions.

I like to place the board in 1 way for long journeys, and in 2 way for playing around.

Setting #2 (Center Point) (Degrees * 10)

This allows the user to set a custom center point. Angle the board to the desired angle and press and release the rear foot button/pad.

I like to set about 2 degrees to the rear foot so that I am level while cruising and the nose is a little higher when climbing bumps.

Settings #3 (Center Zone) (Degrees)

This sets desired cruising angle.

My setting is 7 (3007).

Setting #4 (Angle Power) (Multiplier * 10)

This is the power to apply for angle.

My setting is 3.5 (4035).

Setting #5 (Angle Retain Power) (Multiplier * 10)

This is the power multiplied by each degree over the Center Zone. This is added to the Angle Power.

This should be set high enough to make it difficult to angle beyond the Center Zone, but not to high as to force the board to jerk back and forth.

My setting is 1.5 (5015).

Setting #6 (Overdrive Angle) (Degrees)

This is the angle to start applying over drive too.

My setting is 5 (6005).

Setting #7 (Overdrive Power) (Multiplier * 10 / 5 Seconds)

This is the value to start multiplying by each degree after Overdrive Angle.

This value should be set low enough that the board doesn't feel like it is kicking up when activated.

My Setting is 0.5 (7005).

Setting #8 (Overdrive Max) (Percent of Total Forward Throttle)

This is the maximum overdrive to add. This should not be set to high as to max out the forward throttle. If you max out the thottle, you will lose angle retention.

My setting is 30 (8030).

Setting #9 (Start Power) (Percent of Total Throttle * 10)

This is the initial power sent to the motor when past 0 in either direction.

Usually not needed for most motors, but lets say your motor required 2% power to start to turn under load, you would set a value of 2.0 (9020).

My setting is 0 (9000).

Setting #1 (Way Mode)

This setting determines whether the board move only forward or in both directions. I like to place the board in 1 way for long journeys, and in 2 way for playing around.

1001 = 1 way. The board will only move in the forward direction.
1002 = 2 way. The board will move in both directions.

Setting #2 (Max Power Mode) (Percent)

This setting determines how much of the 100% throttle to utilize in either direction.

Do not exceed 100 (2100)

Setting #3 (Center Zone) (Degrees)

This setting determines, in degrees, how far to tilt the board prior to utilizing the overdrive variable. This is your equal power to tilt zone.

I find a setting of 7 (3007) works best for me. The minimum setting is 5 as their has to be value less then the set value to allow the software to subtract incurred overdrive.

Setting #4 (Center Power) (Multiplied by 10)

This is the equal multiplier for standard power to angle ratio.

Ex: a setting of 30 (4030) will provide 3.0% duty to angle to drive the board. At 10 degrees of angle, the board will be attempting to drive the motor at 30% power.

Setting #5 (Overdrive Power) (Multiplied by 10)

This is the overdrive power variable. This will tell the board to add this value devided by 10 times how many degrees beyond the center zone to the overdrive variable.

Returning the board to an angle less than the center zone will subtract this value devided by 10 times how many degrees inside of the center zone from the overdrive variable.

The higher this setting, The more the board will feal like it is fighting you. This is by design as it naturally pushes the board to the center zone value. Holding the angle past the center zone will steadily increase the boards speed.

Be warned: The incurred overdrive value must be scrubbed before the board can be stopped in the level position. This is simply done by leaning back, but the more overdrive incurred the harder it will be to perform a sudden stop.

Personally I find 5 (5005) to be a good value for this setting.

Setting #6 (Start Power) (Percent) (Multiplied by 10)

This is the initial power sent to the motor when past 0 in either direction.

Usually not needed for most motors, but lets say your motor required 2% power to start to turn under load, you would set a value of 20 (6020).

Setting #7 (Kick Power) (Percent)

This is kick power in percent. This will cause the board to jump this value to the board once it reaches the Max Power Setting minus this value. This is designed to keep the board from traveling beyond the Max Power Setting.

Setting #8 (Factory Reset)

This is the factory reset settings. Hold the back button for atleast 3 secconds under this option to reset all values to their minimum values. The board will then save and exit the settings menu.

This setting is soon to be replaced by the set level option.

Specs & Hardware

Performance

  • Actual Range - 10 km
  • Top Speed - 36.7 km/h (Before I got to scared to go any faster.)
  • Full Charge from Empty - 3amp Charger - Between 2 and 2.5 hours.

Board Dimensions

    Outer Dimensions
  • 30 Inches Long
  • 9.5 Inches Wide
  • 0.5 Inches Thick
    Wheel Cut Out
  • 11 Inches long
  • 6.5 Inches Wide
  • Located at exact center. Due to the location of the tire on the axel,
    the tire will be slightly to the left side. As the tire valve
    is on the right side, this leaves enough clearence. If you ride
    Goofy Foot, you may want to mount the wheel in the opposite direction.
    Aluminum Rails
  • 28 Inches Long
  • 2 Inches Tall
  • 1 Inch Wide
  • 1/8 Inch Wall Thickness
  • 45 Degree Cut on Each End.

Hardware

  • Battery - 12s2p (24 Sansung 18650 25R Cells (Green Batteries)) - Here
  • Hub Motor - 10x6inch 48v 800w (From AliExpress) - Here
  • ESC - Maytech VESC - Here
  • Controller - Arduino Nano V3 - Here
  • Display - TM1637 - Here
  • Gyro - MPU6050 - Here
  • BMS - Deligreen 12s 36v 50amp (Used for Charge Only) - Here
  • Foot Buttons - 16mm Reset screw + High Round - Here
  • Charge Port - 5.5x2.1mm - Here
  • Charger - DC 5.5x2.1 - Here
  • Anti-spark Switch - Maytech MTS1810AS - Here

Pending...

Wiring

My setup utilizes 2 waterproof momentary switches mounted flush to act as the front and rear safety/settings switches. These switches are wired in pull-up configuration. I also utilize a Piezo Buzzer and a TM1637 display module.

Button One (Back Foot Button)

Utilizing a 4k7 resistor I bridge digital pin 6 to 5 volt positive, then wire the switch from pin 6 to ground when pressed.

Button Two (Front Foot Button)

Utilizing a 4k7 resistor I bridge digital pin 7 to 5 volt positive, then wire the switch from pin 7 to ground when pressed.

Piezo Buzzer

The buzzer I wire positive to digital pin 10 and negative to ground.

TM1637 Display

I wire VIN to 5 volt positive, GND to ground, CLK to digital pin 4, and DIO to digital pin 3.

MPU6050 Module

VCC to 5 volt positive, GND to ground, SCL to A5 (SCL), SDA to A4 (SDA).


More Pending...

Arduino Nano Code

Note: disconnect UART cable from VESC before programming Arduino, even if VESC is powered off.

Use these versions of the required libraries. Some have been modified to work better with the Arduino Nano.

Version 1.11 now released.

This new version is now smoother and has a couple of new settings available.

Note: Upgrading to this version will erase your current settings.

BoardApp1-11.ino
404: File Not Found
MPU6050x.ino
//range -32768 to 32767 #include "Wire.h" // This library allows you to communicate with I2C devices. #define MPU_ADDR 0x68 #define GYROM 131 #define ACCM 182 double accFilter (double filterArray[], int filterSize, double &lastFValue, double value, int multiplier) { double fMin = value; double fMax = value; for (int i = 0; i < filterSize; i++) { filterArray[i] = filterArray[i + 1]; if (filterArray[i] < fMin) {fMin = filterArray[i];} if (filterArray[i] > fMax) {fMax = filterArray[i];} } filterArray[filterSize] = value; if (filterArray[filterSize] < fMin) {fMin = filterArray[filterSize];} if (filterArray[filterSize] > fMax) {fMax = filterArray[filterSize];} double fValue = (fMin + fMax) / 2; if (fValue <= lastFValue + multiplier && fValue >= lastFValue - multiplier) { if (fValue < 0 && lastFValue > fValue) {fValue = lastFValue;} else if (fValue > 0 && lastFValue < fValue) {fValue = lastFValue;} else {lastFValue = fValue;} } else {lastFValue = fValue;} return fValue; } void startMPUx () { Wire.begin(); Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties Wire.beginTransmission(MPU_ADDR); // Begins a transmission to the I2C slave (GY-521 board) Wire.write(0x6B); // PWR_MGMT_1 register Wire.write(0x00); // set to zero (wakes up the MPU-6050) Wire.endTransmission(true); } void readMPUx () { static long timer; static bool gyroReady = false; static double gyroXH, gyroXL, gyroXD, gyroXS; static double gRoll; // Gyro static double fRollA[9]; static double fRollL = 0; Wire.beginTransmission(MPU_ADDR); Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H) [MPU-6000 and MPU-6050 Register Map and Descriptions Revision 4.2, p.40] Wire.endTransmission(false); // the parameter indicates that the Arduino will send a restart. As a result, the connection is kept active. Wire.requestFrom(MPU_ADDR, 14, true); // request registers const long cycleTime = timer; timer = millis(); const int cycle = timer - cycleTime; // Time Position // "Wire.read()<<8 | Wire.read();" means two registers are read and stored in the same variable const double accXT = Wire.read()<<8 | Wire.read(); // reading registers: 0x3B (ACCEL_XOUT_H) and 0x3C (ACCEL_XOUT_L) const double accYT = Wire.read()<<8 | Wire.read(); // reading registers: 0x3D (ACCEL_YOUT_H) and 0x3E (ACCEL_YOUT_L) const double accZT = Wire.read()<<8 | Wire.read(); // reading registers: 0x3F (ACCEL_ZOUT_H) and 0x40 (ACCEL_ZOUT_L) const double tempT = Wire.read()<<8 | Wire.read(); // reading registers: 0x41 (TEMP_OUT_H) and 0x42 (TEMP_OUT_L) const double gyroXT = Wire.read()<<8 | Wire.read(); // reading registers: 0x43 (GYRO_XOUT_H) and 0x44 (GYRO_XOUT_L) const double gyroYT = Wire.read()<<8 | Wire.read(); // reading registers: 0x45 (GYRO_YOUT_H) and 0x46 (GYRO_YOUT_L) const double gyroZT = Wire.read()<<8 | Wire.read(); // reading registers: 0x47 (GYRO_ZOUT_H) and 0x48 (GYRO_ZOUT_L) const double aRoll = -atan2(accYT, sqrt((accXT * accXT) + (accZT * accZT))) * 223 / M_PI; const double temp = tempT / 340 + 36.53; if (gyroReady) { const double gyroX = gyroXT - gyroXD; gRoll += gyroX * cycle / GYROM / 1000; double alphaM = 1; if (gRoll > aRoll) {alphaM = gRoll - aRoll;} else if (aRoll > gRoll) {alphaM = aRoll - gRoll;} double alpha = 1 - (0.1 / alphaM); if (alpha > 0.99) {alpha = 0.99;} gRoll = gRoll * alpha + aRoll * (1 - alpha); double fRoll = accFilter(fRollA, 9, fRollL, gRoll, 1); newMpuData(fRoll, temp, cycle); //displayNumber(fRoll * 10); } else { static double lastY; static int sameCount = 0; //Gyro Calibration if (sameCount == 1000) { gyroXD = (gyroXH + gyroXL) / 2; gyroXS = gyroXH - gyroXL; gRoll = aRoll; gyroReady = true; } else if (lastY < aRoll - 2 || lastY > aRoll + 2) { lastY = (int) aRoll; gyroXH = gyroXT; gyroXL = gyroXT; gyroXS = 3; sameCount = 0; } else { if (gyroXT > gyroXH) gyroXH = gyroXT; if (gyroXT < gyroXL) gyroXL = gyroXT; sameCount++; displayNumber(10 - (sameCount / 100)); } } }
TM1637Display.cpp
// Author: avishorp@gmail.com // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA extern "C" { #include <stdlib.h> #include <string.h> #include <inttypes.h> } #include "TM1637Display.h" #include <Arduino.h> #define TM1637_I2C_COMM1 0x40 #define TM1637_I2C_COMM2 0xC0 #define TM1637_I2C_COMM3 0x80 // // A // --- // F | | B // -G- // E | | C // --- // D const uint8_t digitToSegment[] = { // XGFEDCBA 0b00111111, // 0 0b00000110, // 1 0b01011011, // 2 0b01001111, // 3 0b01100110, // 4 0b01101101, // 5 0b01111101, // 6 0b00000111, // 7 0b01111111, // 8 0b01101111, // 9 0b01110111, // A 0b01111100, // b 0b00111001, // C 0b01011110, // d 0b01111001, // E 0b01110001 // F }; static const uint8_t minusSegments = 0b01000000; TM1637Display::TM1637Display(uint8_t pinClk, uint8_t pinDIO, unsigned int bitDelay) { // Copy the pin numbers m_pinClk = pinClk; m_pinDIO = pinDIO; m_bitDelay = bitDelay; // Set the pin direction and default value. // Both pins are set as inputs, allowing the pull-up resistors to pull them up pinMode(m_pinClk, INPUT); pinMode(m_pinDIO,INPUT); digitalWrite(m_pinClk, LOW); digitalWrite(m_pinDIO, LOW); } void TM1637Display::setBrightness(uint8_t brightness, bool on) { m_brightness = (brightness & 0x7) | (on? 0x08 : 0x00); } void TM1637Display::setSegments(const uint8_t segments[], uint8_t length, uint8_t pos) { // Write COMM1 start(); writeByte(TM1637_I2C_COMM1); stop(); // Write COMM2 + first digit address start(); writeByte(TM1637_I2C_COMM2 + (pos & 0x03)); // Write the data bytes for (uint8_t k=0; k < length; k++) writeByte(segments[k]); stop(); // Write COMM3 + brightness start(); writeByte(TM1637_I2C_COMM3 + (m_brightness & 0x0f)); stop(); } void TM1637Display::clear() { uint8_t data[] = { 0, 0, 0, 0 }; setSegments(data); } void TM1637Display::showNumberDec(int num, bool leading_zero, uint8_t length, uint8_t pos) { showNumberDecEx(num, 0, leading_zero, length, pos); } void TM1637Display::showNumberDecEx(int num, uint8_t dots, bool leading_zero, uint8_t length, uint8_t pos) { showNumberBaseEx(num < 0? -10 : 10, num < 0? -num : num, dots, leading_zero, length, pos); } void TM1637Display::showNumberHexEx(uint16_t num, uint8_t dots, bool leading_zero, uint8_t length, uint8_t pos) { showNumberBaseEx(16, num, dots, leading_zero, length, pos); } void TM1637Display::showNumberBaseEx(int8_t base, uint16_t num, uint8_t dots, bool leading_zero, uint8_t length, uint8_t pos) { bool negative = false; if (base < 0) { base = -base; negative = true; } uint8_t digits[4]; if (num == 0 && !leading_zero) { // Singular case - take care separately for(uint8_t i = 0; i < (length-1); i++) digits[i] = 0; digits[length-1] = encodeDigit(0); } else { //uint8_t i = length-1; //if (negative) { // // Negative number, show the minus sign // digits[i] = minusSegments; // i--; //} for(int i = length-1; i >= 0; --i) { uint8_t digit = num % base; if (digit == 0 && num == 0 && leading_zero == false) // Leading zero is blank digits[i] = 0; else digits[i] = encodeDigit(digit); if (digit == 0 && num == 0 && negative) { digits[i] = minusSegments; negative = false; } num /= base; } } if(dots != 0) { showDots(dots, digits); } setSegments(digits, length, pos); } void TM1637Display::bitDelay() { delayMicroseconds(m_bitDelay); } void TM1637Display::start() { pinMode(m_pinDIO, OUTPUT); bitDelay(); } void TM1637Display::stop() { pinMode(m_pinDIO, OUTPUT); bitDelay(); pinMode(m_pinClk, INPUT); bitDelay(); pinMode(m_pinDIO, INPUT); bitDelay(); } bool TM1637Display::writeByte(uint8_t b) { uint8_t data = b; // 8 Data Bits for(uint8_t i = 0; i < 8; i++) { // CLK low pinMode(m_pinClk, OUTPUT); bitDelay(); // Set data bit if (data & 0x01) pinMode(m_pinDIO, INPUT); else pinMode(m_pinDIO, OUTPUT); bitDelay(); // CLK high pinMode(m_pinClk, INPUT); bitDelay(); data = data >> 1; } // Wait for acknowledge // CLK to zero pinMode(m_pinClk, OUTPUT); pinMode(m_pinDIO, INPUT); bitDelay(); // CLK to high pinMode(m_pinClk, INPUT); bitDelay(); uint8_t ack = digitalRead(m_pinDIO); if (ack == 0) pinMode(m_pinDIO, OUTPUT); bitDelay(); pinMode(m_pinClk, OUTPUT); bitDelay(); return ack; } void TM1637Display::showDots(uint8_t dots, uint8_t* digits) { for(int i = 0; i < 4; ++i) { digits[i] |= (dots & 0x80); dots <<= 1; } } uint8_t TM1637Display::encodeDigit(uint8_t digit) { return digitToSegment[digit & 0x0f]; }
TM1637Display.h
// Author: avishorp@gmail.com // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #ifndef __TM1637DISPLAY__ #define __TM1637DISPLAY__ #include <inttypes.h> #define SEG_A 0b00000001 #define SEG_B 0b00000010 #define SEG_C 0b00000100 #define SEG_D 0b00001000 #define SEG_E 0b00010000 #define SEG_F 0b00100000 #define SEG_G 0b01000000 #define SEG_DP 0b10000000 #define DEFAULT_BIT_DELAY 100 class TM1637Display { public: //! Initialize a TM1637Display object, setting the clock and //! data pins. //! //! @param pinClk - The number of the digital pin connected to the clock pin of the module //! @param pinDIO - The number of the digital pin connected to the DIO pin of the module //! @param bitDelay - The delay, in microseconds, between bit transition on the serial //! bus connected to the display TM1637Display(uint8_t pinClk, uint8_t pinDIO, unsigned int bitDelay = DEFAULT_BIT_DELAY); //! Sets the brightness of the display. //! //! The setting takes effect when a command is given to change the data being //! displayed. //! //! @param brightness A number from 0 (lowes brightness) to 7 (highest brightness) //! @param on Turn display on or off void setBrightness(uint8_t brightness, bool on = true); //! Display arbitrary data on the module //! //! This function receives raw segment values as input and displays them. The segment data //! is given as a byte array, each byte corresponding to a single digit. Within each byte, //! bit 0 is segment A, bit 1 is segment B etc. //! The function may either set the entire display or any desirable part on its own. The first //! digit is given by the @ref pos argument with 0 being the leftmost digit. The @ref length //! argument is the number of digits to be set. Other digits are not affected. //! //! @param segments An array of size @ref length containing the raw segment values //! @param length The number of digits to be modified //! @param pos The position from which to start the modification (0 - leftmost, 3 - rightmost) void setSegments(const uint8_t segments[], uint8_t length = 4, uint8_t pos = 0); //! Clear the display void clear(); //! Display a decimal number //! //! Dispaly the given argument as a decimal number. //! //! @param num The number to be shown //! @param leading_zero When true, leading zeros are displayed. Otherwise unnecessary digits are //! blank. NOTE: leading zero is not supported with negative numbers. //! @param length The number of digits to set. The user must ensure that the number to be shown //! fits to the number of digits requested (for example, if two digits are to be displayed, //! the number must be between 0 to 99) //! @param pos The position of the most significant digit (0 - leftmost, 3 - rightmost) void showNumberDec(int num, bool leading_zero = false, uint8_t length = 4, uint8_t pos = 0); //! Display a decimal number, with dot control //! //! Dispaly the given argument as a decimal number. The dots between the digits (or colon) //! can be individually controlled. //! //! @param num The number to be shown //! @param dots Dot/Colon enable. The argument is a bitmask, with each bit corresponding to a dot //! between the digits (or colon mark, as implemented by each module). i.e. //! For displays with dots between each digit: //! * 0.000 (0b10000000) //! * 00.00 (0b01000000) //! * 000.0 (0b00100000) //! * 0.0.0.0 (0b11100000) //! For displays with just a colon: //! * 00:00 (0b01000000) //! For displays with dots and colons colon: //! * 0.0:0.0 (0b11100000) //! @param leading_zero When true, leading zeros are displayed. Otherwise unnecessary digits are //! blank. NOTE: leading zero is not supported with negative numbers. //! @param length The number of digits to set. The user must ensure that the number to be shown //! fits to the number of digits requested (for example, if two digits are to be displayed, //! the number must be between 0 to 99) //! @param pos The position of the most significant digit (0 - leftmost, 3 - rightmost) void showNumberDecEx(int num, uint8_t dots = 0, bool leading_zero = false, uint8_t length = 4, uint8_t pos = 0); //! Display a hexadecimal number, with dot control //! //! Dispaly the given argument as a hexadecimal number. The dots between the digits (or colon) //! can be individually controlled. //! //! @param num The number to be shown //! @param dots Dot/Colon enable. The argument is a bitmask, with each bit corresponding to a dot //! between the digits (or colon mark, as implemented by each module). i.e. //! For displays with dots between each digit: //! * 0.000 (0b10000000) //! * 00.00 (0b01000000) //! * 000.0 (0b00100000) //! * 0.0.0.0 (0b11100000) //! For displays with just a colon: //! * 00:00 (0b01000000) //! For displays with dots and colons colon: //! * 0.0:0.0 (0b11100000) //! @param leading_zero When true, leading zeros are displayed. Otherwise unnecessary digits are //! blank //! @param length The number of digits to set. The user must ensure that the number to be shown //! fits to the number of digits requested (for example, if two digits are to be displayed, //! the number must be between 0 to 99) //! @param pos The position of the most significant digit (0 - leftmost, 3 - rightmost) void showNumberHexEx(uint16_t num, uint8_t dots = 0, bool leading_zero = false, uint8_t length = 4, uint8_t pos = 0); //! Translate a single digit into 7 segment code //! //! The method accepts a number between 0 - 15 and converts it to the //! code required to display the number on a 7 segment display. //! Numbers between 10-15 are converted to hexadecimal digits (A-F) //! //! @param digit A number between 0 to 15 //! @return A code representing the 7 segment image of the digit (LSB - segment A; //! bit 6 - segment G; bit 7 - always zero) uint8_t encodeDigit(uint8_t digit); protected: void bitDelay(); void start(); void stop(); bool writeByte(uint8_t b); void showDots(uint8_t dots, uint8_t* digits); void showNumberBaseEx(int8_t base, uint16_t num, uint8_t dots = 0, bool leading_zero = false, uint8_t length = 4, uint8_t pos = 0); private: uint8_t m_pinClk; uint8_t m_pinDIO; uint8_t m_brightness; unsigned int m_bitDelay; }; #endif // __TM1637DISPLAY__
VescUart.cpp
#include "VescUart.h" #include <HardwareSerial.h> void VescUart::setSerialPort(HardwareSerial* port) {serialPort = port;} int VescUart::receiveUartMessage(uint8_t * payloadReceived) { // Messages <= 255 starts with "2", 2nd byte is length // Messages > 255 starts with "3" 2nd and 3rd byte is length combined with 1st >>8 and then &0xFF uint16_t counter = 0; uint16_t endMessage = 256; bool messageRead = false; uint8_t messageReceived[256]; uint16_t lenPayload = 0; //uint32_t timeout = millis() + 100; // Defining the timestamp for timeout (100ms before timeout) //while ( millis() < timeout && messageRead == false) { while ( messageRead == false) { while (serialPort->available()) { messageReceived[counter++] = serialPort->read(); if (counter == 2) { switch (messageReceived[0]) { case 2: endMessage = messageReceived[1] + 5; //Payload size + 2 for sice + 3 for SRC and End. lenPayload = messageReceived[1]; break; case 3: break; // ToDo: Add Message Handling > 255 (starting with 3) default: break; } } if (counter >= sizeof(messageReceived)) break; if (counter == endMessage && messageReceived[endMessage - 1] == 3) { messageReceived[endMessage] = 0; messageRead = true; break; // Exit if end of message is reached, even if there is still more data in the buffer. } } } bool unpacked = false; if (messageRead) {unpacked = unpackPayload(messageReceived, endMessage, payloadReceived);} if (unpacked) {return lenPayload;} // Message was read else {return 0;} // No Message Read } bool VescUart::unpackPayload(uint8_t * message, int lenMes, uint8_t * payload) { uint16_t crcMessage = 0; uint16_t crcPayload = 0; // Rebuild crc: crcMessage = message[lenMes - 3] << 8; crcMessage &= 0xFF00; crcMessage += message[lenMes - 2]; // Extract payload: memcpy(payload, &message[2], message[1]); crcPayload = crc16(payload, message[1]); if (crcPayload == crcMessage) {return true;} else {return false;} } int VescUart::packSendPayload(uint8_t * payload, int lenPay) { uint16_t crcPayload = crc16(payload, lenPay); int count = 0; uint8_t messageSend[256]; if (lenPay <= 256) { messageSend[count++] = 2; messageSend[count++] = lenPay; } else { messageSend[count++] = 3; messageSend[count++] = (uint8_t)(lenPay >> 8); messageSend[count++] = (uint8_t)(lenPay & 0xFF); } memcpy(&messageSend[count], payload, lenPay); count += lenPay; messageSend[count++] = (uint8_t)(crcPayload >> 8); messageSend[count++] = (uint8_t)(crcPayload & 0xFF); messageSend[count++] = 3; messageSend[count] = '\0'; // Sending package serialPort->write(messageSend, count); // Returns number of send bytes return count; } bool VescUart::processReadPacket(uint8_t * message) { COMM_PACKET_ID packetId; int32_t ind = 0; packetId = (COMM_PACKET_ID)message[0]; message++; // Removes the packetId from the actual message (payload) switch (packetId){ case COMM_GET_VALUES: // Structure defined here: https://github.com/vedderb/bldc/blob/43c3bbaf91f5052a35b75c2ff17b5fe99fad94d1/commands.c#L164 ind = 4; // Skip the first 4 bytes data.avgMotorCurrent = buffer_get_float32(message, 100.0, &ind); data.avgInputCurrent = buffer_get_float32(message, 100.0, &ind); ind += 8; // Skip the next 8 bytes data.dutyCycleNow = buffer_get_float16(message, 1000.0, &ind); data.rpm = buffer_get_int32(message, &ind); data.inpVoltage = buffer_get_float16(message, 10.0, &ind); return true; break; default: return false; break; } } bool VescUart::getVescValues(void) { uint8_t command[1] = { COMM_GET_VALUES }; uint8_t payload[256]; packSendPayload(command, 1); // delay(1); //needed, otherwise data is not read int lenPayload = receiveUartMessage(payload); if (lenPayload > 55) { bool read = processReadPacket(payload); //returns true if sucessful return read; } else { return false; } } void VescUart::setDuty(float duty) { int32_t index = 0; uint8_t payload[5]; payload[index++] = COMM_SET_DUTY; buffer_append_int32(payload, (int32_t)(duty * 100000), &index); packSendPayload(payload, 5); } void VescUart::setCurrent(float current) { int32_t index = 0; uint8_t payload[5]; payload[index++] = COMM_SET_CURRENT; buffer_append_int32(payload, (int32_t)(current * 1000), &index); packSendPayload(payload, 5); } void VescUart::setBrakeCurrent(float brakeCurrent) { int32_t index = 0; uint8_t payload[5]; payload[index++] = COMM_SET_CURRENT_BRAKE; buffer_append_int32(payload, (int32_t)(brakeCurrent * 1000), &index); packSendPayload(payload, 5); }
VescUart.h
#ifndef _VESCUART_h #define _VESCUART_h #include <Arduino.h> #include "datatypes.h" #include "buffer.h" #include "crc.h" class VescUart { /** Struct to store the telemetry data returned by the VESC */ struct dataPackage { float avgMotorCurrent; float avgInputCurrent; float dutyCycleNow; long rpm; float inpVoltage; }; public: /** * @brief Class constructor */ /** Variabel to hold measurements returned from VESC */ dataPackage data; /** * @brief Set the serial port for uart communication * @param port - Reference to Serial port (pointer) */ void setSerialPort(HardwareSerial* port); /** * @brief Set the serial port for debugging * @param port - Reference to Serial port (pointer) */ /** * @brief Sends a command to VESC and stores the returned data * * @return True if successfull otherwise false */ bool getVescValues(void); /** * @brief Sends values for joystick and buttons to the nunchuck app */ void setNunchuckValues(void); /** * @brief Set the current to drive the motor * @param current - The current to apply */ void setCurrent(float current); /** * @brief Set the current to brake the motor * @param brakeCurrent - The current to apply */ void setBrakeCurrent(float brakeCurrent); /** * @brief Set the rpm of the motor * @param rpm - The desired RPM (actually eRPM = RPM * poles) */ void setRPM(float rpm); /** * @brief Set the duty of the motor * @param duty - The desired duty (0.0-1.0) */ void setDuty(float duty); /** * @brief Help Function to print struct dataPackage over Serial for Debug */ void printVescValues(void); private: /** Variabel to hold the reference to the Serial object to use for UART */ HardwareSerial* serialPort = NULL; /** Variabel to hold the reference to the Serial object to use for debugging. * Uses the class Stream instead of HarwareSerial */ Stream* debugPort = NULL; /** * @brief Packs the payload and sends it over Serial * * @param payload - The payload as a unit8_t Array with length of int lenPayload * @param lenPay - Length of payload * @return The number of bytes send */ int packSendPayload(uint8_t * payload, int lenPay); /** * @brief Receives the message over Serial * * @param payloadReceived - The received payload as a unit8_t Array * @return The number of bytes receeived within the payload */ int receiveUartMessage(uint8_t * payloadReceived); /** * @brief Verifies the message (CRC-16) and extracts the payload * * @param message - The received UART message * @param lenMes - The lenght of the message * @param payload - The final payload ready to extract data from * @return True if the process was a success */ bool unpackPayload(uint8_t * message, int lenMes, uint8_t * payload); /** * @brief Extracts the data from the received payload * * @param message - The payload to extract data from * @return True if the process was a success */ bool processReadPacket(uint8_t * message); /** * @brief Help Function to print uint8_t array over Serial for Debug * * @param data - Data array to print * @param len - Lenght of the array to print */ void serialPrint(uint8_t * data, int len); }; #endif
buffer.cpp
/* Copyright 2012-2014 Benjamin Vedder benjamin@vedder.se This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * buffer.c * * Created on: 13 maj 2013 * Author: benjamin */ #include "buffer.h" void buffer_append_int16(uint8_t* buffer, int16_t number, int32_t *index) { buffer[(*index)++] = number >> 8; buffer[(*index)++] = number; } void buffer_append_uint16(uint8_t* buffer, uint16_t number, int32_t *index) { buffer[(*index)++] = number >> 8; buffer[(*index)++] = number; } void buffer_append_int32(uint8_t* buffer, int32_t number, int32_t *index) { buffer[(*index)++] = number >> 24; buffer[(*index)++] = number >> 16; buffer[(*index)++] = number >> 8; buffer[(*index)++] = number; } void buffer_append_uint32(uint8_t* buffer, uint32_t number, int32_t *index) { buffer[(*index)++] = number >> 24; buffer[(*index)++] = number >> 16; buffer[(*index)++] = number >> 8; buffer[(*index)++] = number; } void buffer_append_float16(uint8_t* buffer, float number, float scale, int32_t *index) { buffer_append_int16(buffer, (int16_t)(number * scale), index); } void buffer_append_float32(uint8_t* buffer, float number, float scale, int32_t *index) { buffer_append_int32(buffer, (int32_t)(number * scale), index); } int16_t buffer_get_int16(const uint8_t *buffer, int32_t *index) { int16_t res = ((uint16_t) buffer[*index]) << 8 | ((uint16_t) buffer[*index + 1]); *index += 2; return res; } uint16_t buffer_get_uint16(const uint8_t *buffer, int32_t *index) { uint16_t res = ((uint16_t) buffer[*index]) << 8 | ((uint16_t) buffer[*index + 1]); *index += 2; return res; } int32_t buffer_get_int32(const uint8_t *buffer, int32_t *index) { int32_t res = ((uint32_t) buffer[*index]) << 24 | ((uint32_t) buffer[*index + 1]) << 16 | ((uint32_t) buffer[*index + 2]) << 8 | ((uint32_t) buffer[*index + 3]); *index += 4; return res; } uint32_t buffer_get_uint32(const uint8_t *buffer, int32_t *index) { uint32_t res = ((uint32_t) buffer[*index]) << 24 | ((uint32_t) buffer[*index + 1]) << 16 | ((uint32_t) buffer[*index + 2]) << 8 | ((uint32_t) buffer[*index + 3]); *index += 4; return res; } float buffer_get_float16(const uint8_t *buffer, float scale, int32_t *index) { return (float)buffer_get_int16(buffer, index) / scale; } float buffer_get_float32(const uint8_t *buffer, float scale, int32_t *index) { return (float)buffer_get_int32(buffer, index) / scale; } bool buffer_get_bool(const uint8_t *buffer, int32_t *index) { if (buffer[*index] == 1) { index++; return true; } else { index++; return false; } } void buffer_append_bool(uint8_t *buffer,bool value, int32_t *index) { if (value == true) { buffer[*index] = 1; (*index)++; } else { buffer[*index] = 0; (*index)++; } }
buffer.h
/* Copyright 2012-2014 Benjamin Vedder benjamin@vedder.se This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * buffer.h * * Created on: 13 maj 2013 * Author: benjamin */ #ifndef BUFFER_H_ #define BUFFER_H_ #include <stdint.h> void buffer_append_int16(uint8_t* buffer, int16_t number, int32_t *index); void buffer_append_uint16(uint8_t* buffer, uint16_t number, int32_t *index); void buffer_append_int32(uint8_t* buffer, int32_t number, int32_t *index); void buffer_append_uint32(uint8_t* buffer, uint32_t number, int32_t *index); void buffer_append_float16(uint8_t* buffer, float number, float scale, int32_t *index); void buffer_append_float32(uint8_t* buffer, float number, float scale, int32_t *index); int16_t buffer_get_int16(const uint8_t *buffer, int32_t *index); uint16_t buffer_get_uint16(const uint8_t *buffer, int32_t *index); int32_t buffer_get_int32(const uint8_t *buffer, int32_t *index); uint32_t buffer_get_uint32(const uint8_t *buffer, int32_t *index); float buffer_get_float16(const uint8_t *buffer, float scale, int32_t *index); float buffer_get_float32(const uint8_t *buffer, float scale, int32_t *index); bool buffer_get_bool(const uint8_t *buffer, int32_t *index); void buffer_append_bool(uint8_t *buffer,bool value, int32_t *index); #endif /* BUFFER_H_ */
crc.cpp
/* Copyright 2012-2014 Benjamin Vedder benjamin@vedder.se This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * crc.c * * Created on: 26 feb 2012 * Author: benjamin */ #include "crc.h" // CRC Table const unsigned short crc16_tab[] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; unsigned short crc16(unsigned char *buf, unsigned int len) { unsigned int i; unsigned short cksum = 0; for (i = 0; i < len; i++) { cksum = crc16_tab[(((cksum >> 8) ^ *buf++) & 0xFF)] ^ (cksum << 8); } return cksum; }
crc.h
/* Copyright 2012-2014 Benjamin Vedder benjamin@vedder.se This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * crc.h * * Created on: 26 feb 2012 * Author: benjamin */ #ifndef CRC_H_ #define CRC_H_ /* * Functions */ unsigned short crc16(unsigned char *buf, unsigned int len); #endif /* CRC_H_ */
datatypes.h
/* Copyright 2016 Benjamin Vedder benjamin@vedder.se This file is part of the VESC firmware. The VESC firmware is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The VESC firmware is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #ifndef DATATYPES_H_ #define DATATYPES_H_ #include <stdint.h> #include <stdbool.h> // Data types typedef enum { MC_STATE_OFF = 0, MC_STATE_DETECTING, MC_STATE_RUNNING, MC_STATE_FULL_BRAKE, } mc_state; typedef enum { PWM_MODE_NONSYNCHRONOUS_HISW = 0, // This mode is not recommended PWM_MODE_SYNCHRONOUS, // The recommended and most tested mode PWM_MODE_BIPOLAR // Some glitches occasionally, can kill MOSFETs } mc_pwm_mode; typedef enum { COMM_MODE_INTEGRATE = 0, COMM_MODE_DELAY } mc_comm_mode; typedef enum { SENSOR_MODE_SENSORLESS = 0, SENSOR_MODE_SENSORED, SENSOR_MODE_HYBRID } mc_sensor_mode; typedef enum { FOC_SENSOR_MODE_SENSORLESS = 0, FOC_SENSOR_MODE_ENCODER, FOC_SENSOR_MODE_HALL } mc_foc_sensor_mode; // Auxiliary output mode typedef enum { OUT_AUX_MODE_OFF = 0, OUT_AUX_MODE_ON_AFTER_2S, OUT_AUX_MODE_ON_AFTER_5S, OUT_AUX_MODE_ON_AFTER_10S } out_aux_mode; typedef enum { MOTOR_TYPE_BLDC = 0, MOTOR_TYPE_DC, MOTOR_TYPE_FOC } mc_motor_type; typedef enum { FAULT_CODE_NONE = 0, FAULT_CODE_OVER_VOLTAGE, FAULT_CODE_UNDER_VOLTAGE, FAULT_CODE_DRV, FAULT_CODE_ABS_OVER_CURRENT, FAULT_CODE_OVER_TEMP_FET, FAULT_CODE_OVER_TEMP_MOTOR } mc_fault_code; typedef enum { CONTROL_MODE_DUTY = 0, CONTROL_MODE_SPEED, CONTROL_MODE_CURRENT, CONTROL_MODE_CURRENT_BRAKE, CONTROL_MODE_POS, CONTROL_MODE_HANDBRAKE, CONTROL_MODE_OPENLOOP, CONTROL_MODE_NONE } mc_control_mode; typedef enum { DISP_POS_MODE_NONE = 0, DISP_POS_MODE_INDUCTANCE, DISP_POS_MODE_OBSERVER, DISP_POS_MODE_ENCODER, DISP_POS_MODE_PID_POS, DISP_POS_MODE_PID_POS_ERROR, DISP_POS_MODE_ENCODER_OBSERVER_ERROR } disp_pos_mode; typedef enum { SENSOR_PORT_MODE_HALL = 0, SENSOR_PORT_MODE_ABI, SENSOR_PORT_MODE_AS5047_SPI } sensor_port_mode; typedef struct { float cycle_int_limit; float cycle_int_limit_running; float cycle_int_limit_max; float comm_time_sum; float comm_time_sum_min_rpm; int32_t comms; uint32_t time_at_comm; } mc_rpm_dep_struct; typedef enum { DRV8301_OC_LIMIT = 0, DRV8301_OC_LATCH_SHUTDOWN, DRV8301_OC_REPORT_ONLY, DRV8301_OC_DISABLED } drv8301_oc_mode; typedef enum { DEBUG_SAMPLING_OFF = 0, DEBUG_SAMPLING_NOW, DEBUG_SAMPLING_START, DEBUG_SAMPLING_TRIGGER_START, DEBUG_SAMPLING_TRIGGER_FAULT, DEBUG_SAMPLING_TRIGGER_START_NOSEND, DEBUG_SAMPLING_TRIGGER_FAULT_NOSEND, DEBUG_SAMPLING_SEND_LAST_SAMPLES } debug_sampling_mode; typedef enum { CAN_BAUD_125K = 0, CAN_BAUD_250K, CAN_BAUD_500K, CAN_BAUD_1M } CAN_BAUD; typedef struct { // Switching and drive mc_pwm_mode pwm_mode; mc_comm_mode comm_mode; mc_motor_type motor_type; mc_sensor_mode sensor_mode; // Limits float l_current_max; float l_current_min; float l_in_current_max; float l_in_current_min; float l_abs_current_max; float l_min_erpm; float l_max_erpm; float l_erpm_start; float l_max_erpm_fbrake; float l_max_erpm_fbrake_cc; float l_min_vin; float l_max_vin; float l_battery_cut_start; float l_battery_cut_end; bool l_slow_abs_current; float l_temp_fet_start; float l_temp_fet_end; float l_temp_motor_start; float l_temp_motor_end; float l_temp_accel_dec; float l_min_duty; float l_max_duty; float l_watt_max; float l_watt_min; // Overridden limits (Computed during runtime) float lo_current_max; float lo_current_min; float lo_in_current_max; float lo_in_current_min; float lo_current_motor_max_now; float lo_current_motor_min_now; // Sensorless (bldc) float sl_min_erpm; float sl_min_erpm_cycle_int_limit; float sl_max_fullbreak_current_dir_change; float sl_cycle_int_limit; float sl_phase_advance_at_br; float sl_cycle_int_rpm_br; float sl_bemf_coupling_k; // Hall sensor int8_t hall_table[8]; float hall_sl_erpm; // FOC float foc_current_kp; float foc_current_ki; float foc_f_sw; float foc_dt_us; float foc_encoder_offset; bool foc_encoder_inverted; float foc_encoder_ratio; float foc_motor_l; float foc_motor_r; float foc_motor_flux_linkage; float foc_observer_gain; float foc_observer_gain_slow; float foc_pll_kp; float foc_pll_ki; float foc_duty_dowmramp_kp; float foc_duty_dowmramp_ki; float foc_openloop_rpm; float foc_sl_openloop_hyst; float foc_sl_openloop_time; float foc_sl_d_current_duty; float foc_sl_d_current_factor; mc_foc_sensor_mode foc_sensor_mode; uint8_t foc_hall_table[8]; float foc_sl_erpm; bool foc_sample_v0_v7; bool foc_sample_high_current; float foc_sat_comp; bool foc_temp_comp; float foc_temp_comp_base_temp; float foc_current_filter_const; // Speed PID float s_pid_kp; float s_pid_ki; float s_pid_kd; float s_pid_kd_filter; float s_pid_min_erpm; bool s_pid_allow_braking; // Pos PID float p_pid_kp; float p_pid_ki; float p_pid_kd; float p_pid_kd_filter; float p_pid_ang_div; // Current controller float cc_startup_boost_duty; float cc_min_current; float cc_gain; float cc_ramp_step_max; // Misc int32_t m_fault_stop_time_ms; float m_duty_ramp_step; float m_current_backoff_gain; uint32_t m_encoder_counts; sensor_port_mode m_sensor_port_mode; bool m_invert_direction; drv8301_oc_mode m_drv8301_oc_mode; int m_drv8301_oc_adj; float m_bldc_f_sw_min; float m_bldc_f_sw_max; float m_dc_f_sw; float m_ntc_motor_beta; out_aux_mode m_out_aux_mode; } mc_configuration; // Applications to use typedef enum { APP_NONE = 0, APP_PPM, APP_ADC, APP_UART, APP_PPM_UART, APP_ADC_UART, APP_NUNCHUK, APP_NRF, APP_CUSTOM } app_use; // Throttle curve mode typedef enum { THR_EXP_EXPO = 0, THR_EXP_NATURAL, THR_EXP_POLY } thr_exp_mode; // PPM control types typedef enum { PPM_CTRL_TYPE_NONE = 0, PPM_CTRL_TYPE_CURRENT, PPM_CTRL_TYPE_CURRENT_NOREV, PPM_CTRL_TYPE_CURRENT_NOREV_BRAKE, PPM_CTRL_TYPE_DUTY, PPM_CTRL_TYPE_DUTY_NOREV, PPM_CTRL_TYPE_PID, PPM_CTRL_TYPE_PID_NOREV } ppm_control_type; typedef struct { ppm_control_type ctrl_type; float pid_max_erpm; float hyst; float pulse_start; float pulse_end; float pulse_center; bool median_filter; bool safe_start; float throttle_exp; float throttle_exp_brake; thr_exp_mode throttle_exp_mode; float ramp_time_pos; float ramp_time_neg; bool multi_esc; bool tc; float tc_max_diff; } ppm_config; // ADC control types typedef enum { ADC_CTRL_TYPE_NONE = 0, ADC_CTRL_TYPE_CURRENT, ADC_CTRL_TYPE_CURRENT_REV_CENTER, ADC_CTRL_TYPE_CURRENT_REV_BUTTON, ADC_CTRL_TYPE_CURRENT_REV_BUTTON_BRAKE_ADC, ADC_CTRL_TYPE_CURRENT_NOREV_BRAKE_CENTER, ADC_CTRL_TYPE_CURRENT_NOREV_BRAKE_BUTTON, ADC_CTRL_TYPE_CURRENT_NOREV_BRAKE_ADC, ADC_CTRL_TYPE_DUTY, ADC_CTRL_TYPE_DUTY_REV_CENTER, ADC_CTRL_TYPE_DUTY_REV_BUTTON, ADC_CTRL_TYPE_PID, ADC_CTRL_TYPE_PID_REV_CENTER, ADC_CTRL_TYPE_PID_REV_BUTTON } adc_control_type; typedef struct { adc_control_type ctrl_type; float hyst; float voltage_start; float voltage_end; float voltage_center; float voltage2_start; float voltage2_end; bool use_filter; bool safe_start; bool cc_button_inverted; bool rev_button_inverted; bool voltage_inverted; bool voltage2_inverted; float throttle_exp; float throttle_exp_brake; thr_exp_mode throttle_exp_mode; float ramp_time_pos; float ramp_time_neg; bool multi_esc; bool tc; float tc_max_diff; uint32_t update_rate_hz; } adc_config; // Nunchuk control types typedef enum { CHUK_CTRL_TYPE_NONE = 0, CHUK_CTRL_TYPE_CURRENT, CHUK_CTRL_TYPE_CURRENT_NOREV } chuk_control_type; typedef struct { chuk_control_type ctrl_type; float hyst; float ramp_time_pos; float ramp_time_neg; float stick_erpm_per_s_in_cc; float throttle_exp; float throttle_exp_brake; thr_exp_mode throttle_exp_mode; bool multi_esc; bool tc; float tc_max_diff; } chuk_config; // NRF Datatypes typedef enum { NRF_SPEED_250K = 0, NRF_SPEED_1M, NRF_SPEED_2M } NRF_SPEED; typedef enum { NRF_POWER_M18DBM = 0, NRF_POWER_M12DBM, NRF_POWER_M6DBM, NRF_POWER_0DBM, NRF_POWER_OFF } NRF_POWER; typedef enum { NRF_AW_3 = 0, NRF_AW_4, NRF_AW_5 } NRF_AW; typedef enum { NRF_CRC_DISABLED = 0, NRF_CRC_1B, NRF_CRC_2B } NRF_CRC; typedef enum { NRF_RETR_DELAY_250US = 0, NRF_RETR_DELAY_500US, NRF_RETR_DELAY_750US, NRF_RETR_DELAY_1000US, NRF_RETR_DELAY_1250US, NRF_RETR_DELAY_1500US, NRF_RETR_DELAY_1750US, NRF_RETR_DELAY_2000US, NRF_RETR_DELAY_2250US, NRF_RETR_DELAY_2500US, NRF_RETR_DELAY_2750US, NRF_RETR_DELAY_3000US, NRF_RETR_DELAY_3250US, NRF_RETR_DELAY_3500US, NRF_RETR_DELAY_3750US, NRF_RETR_DELAY_4000US } NRF_RETR_DELAY; typedef struct { NRF_SPEED speed; NRF_POWER power; NRF_CRC crc_type; NRF_RETR_DELAY retry_delay; unsigned char retries; unsigned char channel; unsigned char address[3]; bool send_crc_ack; } nrf_config; typedef struct { // Settings uint8_t controller_id; uint32_t timeout_msec; float timeout_brake_current; bool send_can_status; uint32_t send_can_status_rate_hz; CAN_BAUD can_baud_rate; // Application to use app_use app_to_use; // PPM application settings ppm_config app_ppm_conf; // ADC application settings adc_config app_adc_conf; // UART application settings uint32_t app_uart_baudrate; // Nunchuk application settings chuk_config app_chuk_conf; // NRF application settings nrf_config app_nrf_conf; } app_configuration; // Communication commands typedef enum { COMM_FW_VERSION = 0, COMM_JUMP_TO_BOOTLOADER, COMM_ERASE_NEW_APP, COMM_WRITE_NEW_APP_DATA, COMM_GET_VALUES, COMM_SET_DUTY, COMM_SET_CURRENT, COMM_SET_CURRENT_BRAKE, COMM_SET_RPM, COMM_SET_POS, COMM_SET_HANDBRAKE, COMM_SET_DETECT, COMM_SET_SERVO_POS, COMM_SET_MCCONF, COMM_GET_MCCONF, COMM_GET_MCCONF_DEFAULT, COMM_SET_APPCONF, COMM_GET_APPCONF, COMM_GET_APPCONF_DEFAULT, COMM_SAMPLE_PRINT, COMM_TERMINAL_CMD, COMM_PRINT, COMM_ROTOR_POSITION, COMM_EXPERIMENT_SAMPLE, COMM_DETECT_MOTOR_PARAM, COMM_DETECT_MOTOR_R_L, COMM_DETECT_MOTOR_FLUX_LINKAGE, COMM_DETECT_ENCODER, COMM_DETECT_HALL_FOC, COMM_REBOOT, COMM_ALIVE, COMM_GET_DECODED_PPM, COMM_GET_DECODED_ADC, COMM_GET_DECODED_CHUK, COMM_FORWARD_CAN, COMM_SET_CHUCK_DATA, COMM_CUSTOM_APP_DATA, COMM_NRF_START_PAIRING } COMM_PACKET_ID; // CAN commands typedef enum { CAN_PACKET_SET_DUTY = 0, CAN_PACKET_SET_CURRENT, CAN_PACKET_SET_CURRENT_BRAKE, CAN_PACKET_SET_RPM, CAN_PACKET_SET_POS, CAN_PACKET_FILL_RX_BUFFER, CAN_PACKET_FILL_RX_BUFFER_LONG, CAN_PACKET_PROCESS_RX_BUFFER, CAN_PACKET_PROCESS_SHORT_BUFFER, CAN_PACKET_STATUS, CAN_PACKET_SET_CURRENT_REL, CAN_PACKET_SET_CURRENT_BRAKE_REL, CAN_PACKET_SET_CURRENT_HANDBRAKE, CAN_PACKET_SET_CURRENT_HANDBRAKE_REL } CAN_PACKET_ID; // Logged fault data typedef struct { mc_fault_code fault; float current; float current_filtered; float voltage; float duty; float rpm; int tacho; int cycles_running; int tim_val_samp; int tim_current_samp; int tim_top; int comm_step; float temperature; int drv8301_faults; } fault_data; // External LED state typedef enum { LED_EXT_OFF = 0, LED_EXT_NORMAL, LED_EXT_BRAKE, LED_EXT_TURN_LEFT, LED_EXT_TURN_RIGHT, LED_EXT_BRAKE_TURN_LEFT, LED_EXT_BRAKE_TURN_RIGHT, LED_EXT_BATT } LED_EXT_STATE; typedef struct { int js_x; int js_y; int acc_x; int acc_y; int acc_z; bool bt_c; bool bt_z; } chuck_data; /* typedef struct { int id; systime_t rx_time; float rpm; float current; float duty; } can_status_msg; */ typedef struct { uint8_t js_x; uint8_t js_y; bool bt_c; bool bt_z; bool bt_push; float vbat; } mote_state; typedef enum { MOTE_PACKET_BATT_LEVEL = 0, MOTE_PACKET_BUTTONS, MOTE_PACKET_ALIVE, MOTE_PACKET_FILL_RX_BUFFER, MOTE_PACKET_FILL_RX_BUFFER_LONG, MOTE_PACKET_PROCESS_RX_BUFFER, MOTE_PACKET_PROCESS_SHORT_BUFFER, MOTE_PACKET_PAIRING_INFO } MOTE_PACKET; typedef struct { float v_in; float temp_mos1; float temp_mos2; float temp_mos3; float temp_mos4; float temp_mos5; float temp_mos6; float temp_pcb; float current_motor; float current_in; float rpm; float duty_now; float amp_hours; float amp_hours_charged; float watt_hours; float watt_hours_charged; int tachometer; int tachometer_abs; mc_fault_code fault_code; } mc_values; typedef enum { NRF_PAIR_STARTED = 0, NRF_PAIR_OK, NRF_PAIR_FAIL } NRF_PAIR_RES; #endif /* DATATYPES_H_ */

3D Prints

Rail Ends

Best printed with TPU, this part doubles to protect the aluminum rail ends and for breaking.

Download

Pending...

VESC Setup (Pending)

This is coming soon, sorry.

Side Projects


Base Code For Micro Projects (No Display, One Arm Switch/Pad)

This is Firmware Version 1.11 cut down for smaller projects.

The board will beep when the Gyro is calibrated and beep again when Armed. During operation, the board will beep when 85% of max speed is reached in either direction.

Requires a piezo buzzer. (The one found in most PCs or are provided with new computer cases.)

Adjust your settings at the top of the code. Add your drive routine near the bottom inside the motorRoutine routine.

Download


CLIENT LOGIN USER: Guest Brantford PC Proudly Canadian CAMM Services Brantford PC Norfolk County PC R.D. Cookson Disposal Ltd. Robert Jones Marine IQ Exhibit