Current Project: 4wd bluetooth control of mechanum wheels.
Hi all - the forums have been a great help (esp. serial input basics - Robin2!) and I have a working proof of concept prototype on the breadboard with code that compiles and controls the motors.
I'm hoping someone has some experience to share on motor behavior which is confusing me.
I'm getting a lot of 'whine' on the motors which stay stalled up until about 70/255 duty cycle after which they start spinning fast enough that it loses traction until I back off the power to 30-40/255. The 'whine' is apparent through all duty cycles but appears to lessen from 200/255 onwards (might just be drowned out by the cheap plastic gearboxes though...)
I'm using little tt motors so can live with the traction loss until the motors are upgraded but I've found that if I hook up one of these motors to a standalone motor control (not MCU based) I get no 'whine' and a very smooth startup from 10-15/255 duty cycle and smooth transitions right through to full power.
I don't know what chip the standalone motor controller uses as its a little black box but looking at the pcb is looks like a standard h-bridge with a pot, the mosfets are wrapped both sides in heatsink so no part number. I do know that it runs its PWM at 15kHz while the nano is 490Hz (except for the one I have on pin 6 which is 960Hz).
What I've read is that a high pwm takes out the whine at the expense of a decreased motor efficiency as its take more effort to saturate the controller ena input - but the motor starts up at a lower duty cycle and has smooth control seemingly with the higher frequency. from what I've read so far - the lower pwm frequency shouldn't affect the cut-in voltage of the motor, leading me to think I have a design (code or hardware) issue, I just have no idea of what it could be.
Before I start messing with nano's timer registers to adjust pwm - I'm hoping someone has some suggestions / advice.
code below, I'll try and append a schematic
Thanks for any suggestions - I'm relearning some very rusty coding skills so all feedback appreciated.
#include <SoftwareSerial.h>
SoftwareSerial SerialMotor(4, 3); // RX, TX
//Housekeeping timers
unsigned long startMillis;
unsigned long currentMillis;
const unsigned long period = 1000; // # of millis wait to execute
static byte STX = 0x3C; // start of frame marker
static byte ETX = 0x3E; // end of frame marker
const byte numBytes = 32; // max size of data frame
byte receivedBytes[numBytes]; // global array to hold the received data
byte byteCount = 0; // number of bytes received in frame
boolean newData = false; // flag for new data received from controller
// Motor A block
#define motorAENA 9 // motor A ENA PIN
#define motorAIN1 14 // directional control pin
#define motorAIN2 15 // directional control pin
// Motor B block
#define motorBENA 10 // motor A ENA PIN
#define motorBIN1 16 // directional control pin
#define motorBIN2 17 // directional control pin
// Motor C block
#define motorCENA 11 // motor A ENA PIN
#define motorCIN1 18 // directional control pin
#define motorCIN2 19 // directional control pin
// Motor D block
#define motorDENA 6 // motor A ENA PIN
#define motorDIN1 12 // directional control pin
#define motorDIN2 13 // directional control pin
void recvBytesWithStartEndMarkers() {
static boolean recvInProgress = false;
static byte ndx = 0;
byte startMarker = 0x3C;
byte endMarker = 0x3E;
byte rb;
while (SerialMotor.available() > 0 && newData == false) {
rb = SerialMotor.read();
if (recvInProgress == true) {
if (rb != endMarker) {
receivedBytes[ndx] = rb;
ndx++;
if (ndx >= numBytes) {
ndx = numBytes - 1;
}
}
else {
receivedBytes[ndx] = '\0'; // terminate the string
recvInProgress = false;
byteCount = ndx; // save the number for use when printing
ndx = 0;
newData = true;
}
}
else if (rb == startMarker) {
recvInProgress = true;
}
}
}
void setup()
{
// Open serial communications and wait for port to open:
Serial.begin(28800);
Serial.println("Goodnight moon!");
// set the data rate for the SoftwareSerial port
SerialMotor.begin(28800);
pinMode(motorAIN1, OUTPUT);
pinMode(motorAIN2, OUTPUT);
pinMode(motorBENA, OUTPUT);
pinMode(motorBIN1, OUTPUT);
pinMode(motorBIN2, OUTPUT);
pinMode(motorCENA, OUTPUT);
pinMode(motorCIN1, OUTPUT);
pinMode(motorCIN2, OUTPUT);
pinMode(motorDENA, OUTPUT);
pinMode(motorDIN1, OUTPUT);
pinMode(motorDIN2, OUTPUT);
}
void motorControl() {
// frame structure HDR A1 A2 B1 B2 C1 C2 D1 D2 CS
// motorN.1 = direction control
// motorN.2 = speed control
Serial.print("motorControl() called"); // debug code to serial monitor
Serial.println(); // debug code to serial monitor
Serial.print("frame structure into motorControl = ");
for (int i = 0; i < byteCount; i++) {
Serial.print(receivedBytes[i], HEX);
Serial.print(" ");
}
Serial.println();
switch (receivedBytes[1]) { // Motor A control
case 0:
Serial.println("case 0 - all brake on motor a"); // debug code to serial monitor
digitalWrite(motorAIN1, HIGH);
digitalWrite(motorAIN2, HIGH);
analogWrite(motorAENA, 0); // future chnage - benchtest current draw before ENA @ 255 for powered brake
return;
case 1:
digitalWrite(motorAIN1, LOW);
digitalWrite(motorAIN2, HIGH);
analogWrite(motorAENA, receivedBytes[2]);
return;
case 2:
digitalWrite(motorAIN1, HIGH);
digitalWrite(motorAIN2, LOW);
analogWrite(motorAENA, receivedBytes[2]);
return;
}
switch (receivedBytes[3]) { // Motor B control
case 0:
digitalWrite(motorBIN1, HIGH);
digitalWrite(motorBIN2, HIGH);
analogWrite(motorBENA, 0);
return;
case 1:
digitalWrite(motorBIN1, LOW);
digitalWrite(motorBIN2, HIGH);
analogWrite(motorBENA, receivedBytes[4]);
return;
case 2:
digitalWrite(motorBIN1, HIGH);
digitalWrite(motorBIN2, LOW);
analogWrite(motorBENA, receivedBytes[4]);
return;
}
switch (receivedBytes[5]) { // Motor C control
case 0:
digitalWrite(motorCIN1, HIGH);
digitalWrite(motorBIN2, HIGH);
analogWrite(motorCENA, 0);
return;
case 1:
digitalWrite(motorCIN1, LOW);
digitalWrite(motorCIN2, HIGH);
analogWrite(motorCENA, receivedBytes[6]);
return;
case 2:
digitalWrite(motorBIN1, HIGH);
digitalWrite(motorBIN2, LOW);
analogWrite(motorCENA, receivedBytes[6]);
return;
}
switch (receivedBytes[7]) { // Motor D control
case 0:
digitalWrite(motorDIN1, HIGH);
digitalWrite(motorDIN2, HIGH);
analogWrite(motorDENA, 0);
return;
case 1:
digitalWrite(motorDIN1, LOW);
digitalWrite(motorDIN2, HIGH);
analogWrite(motorDENA, receivedBytes[8]);
return;
case 2:
digitalWrite(motorDIN1, HIGH);
digitalWrite(motorDIN2, LOW);
analogWrite(motorDENA, receivedBytes[8]);
return;
}
newData = false; // set newData to false after frame execution
memset(receivedBytes, 0, numBytes); // debug code - 0 write frame data after execution
}
boolean validateData() {
Serial.print("RX command frame from controller "); // debug code output received frame to serial monitor
for (byte n = 0; n < byteCount; n++) {
Serial.print(receivedBytes[n], HEX);
Serial.print(' ');
}
byte checkSum = 0; // initialize checksum at 0
for (int i = 0; i < byteCount - 1; i++) {
checkSum = checkSum + receivedBytes[i];
}
checkSum = checkSum % 10;
if (checkSum == receivedBytes[byteCount - 1]) {
Serial.print("Good checkSum = "); Serial.print(checkSum, HEX); // debug code to serial monitor
byte checkFrame[] = { STX, checkSum, ETX }; // controlled expected ACK response frame
SerialMotor.write(checkFrame, 3);
Serial.print(" checkFrame = "); // debug code to serial monitor
for (int i = 0; i < byteCount - 1; i++) { // debug code to serial monitor
Serial.print(checkFrame[i], HEX);
Serial.print(" ");
}
newData = false;
return true;
}
else {
Serial.print("Bad checkSum = "); Serial.print(checkSum, HEX); // debug code to serial monitor
byte checkFrame[] = { STX, checkSum, ETX }; // ntf - future change, convert to resend request and match in controller code
SerialMotor.write(checkFrame, 3);
Serial.print(" checkFrame = "); // debug code to serial monitor
for (int i = 0; i < byteCount - 1; i++) { // debug code to serial monitor
Serial.print(checkFrame[i], HEX);
Serial.print(" ");
newData = false;
return false;
}
}
}
void parseData() {
// read header (HDR) from input frame and action based on lookup table
// HDR = 1 - motor control
if (newData == true) {
if (validateData() == true) { // verify checksum and send frame ack if good
switch (receivedBytes[0]) {
case 1:
motorControl();
return;
}
}
}
}
void loop() {
recvBytesWithStartEndMarkers();
if (newData == true) { parseData(); }
/*
currentMillis = millis();
if (currentMillis - startMillis >= period) {
Serial.println("heartbeat"); // debug code keepawake serialmon
startMillis = currentMillis;
}
*/
