I'm a .NET developer with little knowledge of c++ and even less of electronics. I took on this project thinking it would be fun and educational. So far, I've learned how terrible I am at it and that's not fun. The scope of the project is that I have a tablet running a .NET application which uses GPS to get position and speed. It then feeds that to the Arduino (via Serial over Bluetooth) to adjust the speed of an auger motor and control other functions of the machine. There are various other sensors that I'm interfacing with, but the issues I'm having revolve around the serial communication and driving the stepper motor. I'm using a Mega2560v3.
The stepper is a NEMA 42 size 4-wire using a microstepping driver. The reasons we're using a stepper to drive an auger are that a> we had one on hand and b> we need to know approximately how many revolutions it makes during use. I'm powering it with a benchtop power supply providing 32 volts, and 5 Amps. Using delayMicroseconds it operates ok, but seems like there could be improvements made. I tried adapting the BlinkWithoutDelay code, but for some reason it was terribly slow (like 10 seconds to do a full rev at the fastest speed) so I went back to delay.
Anyway, the biggest issue is that when I send any serial command (via the tablet app, the arduino serial monitor and usb, or a bluetooth terminal on my phone) the program pauses (killing the motor) for about .5 seconds. Can someone please point me in the right direction as to how to solve this? I don't have much hair left, so I'd like to keep what I have. I'm WAY past my deadline and don't know where to go from here.
Thanks in advance for any help or suggestions/improvements anyone can provide.
//pins
const int dirPin = 8;
const int stepPin = 9;
const int seedPin = A8;
const int levelPin = 53;
const int machPin = 52;
const int safePin = 51;
//ops
int doRun = 0;
int spdValue = 3600;
int currDelay = 3600;
int maxDelay = 3600;
int stepState = LOW;
int val = 0;
int inVal = 0;
byte index = 0;
int isOn = 1;
unsigned long onStart = 0;
unsigned long onTime = 0;
unsigned long offStart = 0;
unsigned long offTime = 0;
#define INPUT_SIZE 30
void setup() {
Serial.begin(9600);
pinMode(levelPin, INPUT);
pinMode(safePin, INPUT);
pinMode(machPin, OUTPUT);
digitalWrite(machPin, LOW);
pinMode(dirPin, OUTPUT);
pinMode(stepPin, OUTPUT);
digitalWrite(dirPin, HIGH);
digitalWrite(stepPin, LOW);
}
void loop() {
if (digitalRead(levelPin) == LOW || digitalRead(safePin) == LOW) {
haltMachine();
} else {
if (Serial.available() > 0) {
char input[INPUT_SIZE + 1];
byte size = Serial.readBytes(input, INPUT_SIZE);
if (size > 0) {
input[size] = 0;
parseBT(input);
}
}
if (doRun == 1) {
digitalWrite(machPin, HIGH);
//checkSeedSensor(); //disable for testing
stepper();
}
}
}
//read bluetooth - [type]:[value]
void parseBT(char* command) {
if (strcmp(command, "gophello") == 0) {
Serial.println("gophello");
} else {
char* separator = strchr(command, ':');
if (separator != 0)
{
*separator = 0;
++separator;
inVal = atoi(separator);
if (strcmp(command, "spd") == 0) {
if (doRun == 1) {
spdValue = inVal;
} else {
spdValue = 3600;
}
} else if (strcmp(command, "run") == 0) {
doRun = inVal;
if (doRun == 0) {
haltMachine();
}
}
}
}
}
//turn the auger
void stepper() {
if (spdValue < 3600) {
//very basic linear accelleration
maxDelay = spdValue;
if (currDelay < maxDelay) {
currDelay++;
}
else if (currDelay > maxDelay) {
currDelay--;
}
delayMicroseconds(currDelay);
digitalWrite(stepPin, HIGH);
delayMicroseconds(currDelay);
digitalWrite(stepPin, LOW);
}
}
//stop/reset everything
void haltMachine() {
maxDelay = 3600;
currDelay = 3600;
spdValue = 3600;
doRun = 0;
digitalWrite(machPin, LOW);
}
//check if "blinking". Stop if on or off solid for too long.
void checkSeedSensor() {
unsigned long currentMillis = millis();
if (analogRead(seedPin) > 900) {
offStart = 0;
offTime = 0;
if (isOn == 0) {
onStart = currentMillis;
isOn = 1;
}
onTime = currentMillis - onStart;
if (onTime > 1000) {
Serial.print("WARNING: Seed sensor has been on for ");
Serial.print(onTime);
Serial.println(" milliseconds");
haltMachine();
}
} else {
onStart = 0;
onTime = 0;
if (isOn == 1) {
offStart = currentMillis;
isOn = 0;
}
offTime = currentMillis - offStart;
if (offTime > 1000) {
Serial.print("WARNING: Seed sensor has been off for ");
Serial.print(offTime);
Serial.println(" milliseconds");
haltMachine();
}
}
}
the program pauses (killing the motor) for about .5 seconds
No the program waits until all the serial data has come in, because that is what you told it to do.
If you want to avoid this then don't read the serial port until serial available tells you all the bytes are in.
Alternately write you code as a state machine. The Blink without delay is an example in the IDE, there are also many sites on this, one is here:- http://www.thebox.myzen.co.uk/Tutorial/State_Machine.html
You are asking readBytes to read 30 characters, which it will try to do. If it doesn't read 30 chars it will timeout after one second because you didn't tell it to do anything different.
If the data being sent end with a linefeed, you would be better off using:
Thanks, guys. I've updated my serial read code based on the examples you provided. I left the stepper code as is because I think it will work well enough for my purpose. If it fails after putting it all together I'll look more into the state machine examples to do things properly. Now that I have the serial read working properly I was also able to tweak the stepper acceleration code. It's probably much more convoluted than it needs to be, but it works.
Here's my updated code:
//pins
const int dirPin = 8;
const int stepPin = 9;
const int seedPin = A8;
const int levelPin = 53;
const int machPin = 52;
const int safePin = 51;
//ops
int doRun = 0;
int spdValue = 2000;
int currDelay = 2000;
int maxDelay = 2000;
int oldDelay = 2000;
int val = 0;
int inVal = 0;
int cycle = 0;
int stepCount = 0;
long revCount = 0;
byte index = 0;
int isOn = 1;
unsigned long onStart = 0;
unsigned long onTime = 0;
unsigned long offStart = 0;
unsigned long offTime = 0;
const unsigned INPUT_SIZE = 30;
void setup() {
Serial.begin(115200);
pinMode(levelPin, INPUT);
pinMode(safePin, INPUT);
pinMode(machPin, OUTPUT);
digitalWrite(machPin, LOW);
pinMode(dirPin, OUTPUT);
pinMode(stepPin, OUTPUT);
digitalWrite(dirPin, HIGH);
digitalWrite(stepPin, LOW);
}
void loop() {
if (digitalRead(levelPin) == LOW || digitalRead(safePin) == LOW) {
haltMachine();
} else {
if (Serial.available() > 0) {
processIncomingByte(Serial.read());
}
if (doRun == 1) {
digitalWrite(machPin, HIGH);
//checkSeedSensor();
stepper();
}
}
}
void processIncomingByte(const byte inByte)
{
static char input_line[INPUT_SIZE];
static unsigned int input_pos = 0;
switch(inByte)
{
case '\n':
input_line[input_pos] = 0;
parseBT(input_line);
input_pos = 0;
break;
case '\r':
break;
default:
if (input_pos < (INPUT_SIZE - 1))
input_line[input_pos++] = inByte;
break;
}
}
//[type]:[value]
void parseBT(const char* command) {
if (strcmp(command, "gophello") == 0) {
Serial.println("gophello");
} else {
char* separator = strchr(command, ':');
if (separator != 0)
{
*separator = 0;
++separator;
inVal = atoi(separator);
if (strcmp(command, "spd") == 0) {
if (doRun == 1) {
spdValue = inVal;
} else {
spdValue = 2000;
}
} else if (strcmp(command, "run") == 0) {
doRun = inVal;
if (doRun == 0) {
haltMachine();
}
}
}
}
}
void stepper() {
if (spdValue < 2000) {
if (spdValue > 1999) {
maxDelay = 1999; //slowest
} else if (spdValue < 50) {
maxDelay = 50; //fastest motor can handle
} else {
maxDelay = spdValue;
}
if (maxDelay != currDelay) {
if (currDelay != oldDelay) {
cycle = 0;
oldDelay = currDelay;
}
if (cycle >= calcCycles(spdValue)) {
if (currDelay < maxDelay) {
currDelay++;
}
else if (currDelay > maxDelay) {
currDelay--;
}
}
cycle++;
}
delayMicroseconds(currDelay);
digitalWrite(stepPin, HIGH);
delayMicroseconds(currDelay);
digitalWrite(stepPin, LOW);
stepCount++;
if (stepCount == 1600) {
stepCount = 0;
revCount++;
Serial.println(revCount);
}
}
}
int calcCycles(int spd) {
int result;
if ((currDelay < spd) || (currDelay > 100)) {
result = 0;
} else {
result = map(currDelay, 50, 100, 3000, 1);
}
return result;
}
void haltMachine() {
maxDelay = 2000;
currDelay = 2000;
spdValue = 2000;
cycle = 0;
doRun = 0;
digitalWrite(machPin, LOW);
}
void checkSeedSensor() {
unsigned long currentMillis = millis();
if (analogRead(seedPin) > 900) {
offStart = 0;
offTime = 0;
if (isOn == 0) {
onStart = currentMillis;
isOn = 1;
}
onTime = currentMillis - onStart;
if (onTime > 1000) {
Serial.print("WARNING: Seed sensor has been on for ");
Serial.print(onTime);
Serial.println(" milliseconds");
haltMachine();
}
} else {
onStart = 0;
onTime = 0;
if (isOn == 1) {
offStart = currentMillis;
isOn = 0;
}
offTime = currentMillis - offStart;
if (offTime > 1000) {
Serial.print("WARNING: Seed sensor has been off for ");
Serial.print(offTime);
Serial.println(" milliseconds");
haltMachine();
}
}
}
Thanks, again, guys. Much appreciated.
PaulS:
The stepper is a NEMA 42 size
But, is it blue?
I'm amazed at the number of people that tell us about the mounting hole standard, as if that meant anything.
I'm amazed at the number of people that make posts with no value, as if we're supposed to be impressed by your post count.
I'm amazed at the number of people that make posts with no value,
The value was for you, and others, to realise that the phrase "NEMA 42" conveys nothing in electronic terms and is, as such, redundant.
It does not say anything about the :-
coil resistance
the current rating of the windings
the configuration of the windings
the torque of the motor
the maximum speed
What it does say is the physical pitch of the mounting screws and in this context is entirely useless.
That post was an attempt to tell you this. This forum is all about education, please do not resent it when people try and do this.
justsomedude:
I'm amazed at the number of people that make posts with no value, as if we're supposed to be impressed by your post count.
The value of PaulS's posts is that, rather than just giving people the answer, he encourages them to work out things for themselves. This can involves giving hints, or asking questions designed to get the poster to think about a problem area. Then next time they can find the answer for themselves.
His high post count indicates that he does that a lot.
justsomedude:
Anyway, the biggest issue is that when I send any serial command (via the tablet app, the arduino serial monitor and usb, or a bluetooth terminal on my phone) the program pauses (killing the motor) for about .5 seconds.
I know you have made a lot of progress on this point.
I have things arranged so the data sent from the PC (over USB) is less than 64 bytes so it fits in the Seral input buffer.
Just before the Arduino starts to move the motors it requests another packet from the PC. By the time the move is complete all the data is waiting in the input buffer and it can be read extremely quickly. With a bit of care this can provide a seamless stream of data for the motors.
My Arduino is happy to talk to the PC (over USB) at 1,000,000 baud or 500,000 or 230,400 - but none of these works with the Arduino Serial monitor.
I amexperimenting with modifying HardwareSerial to increase the buffer size. It should allow more data to be transferred in the same time frame when account is taken of the time required by the request from the Arduino.
Robin2:
I amexperimenting with modifying HardwareSerial to increase the buffer size. It should allow more data to be transferred in the same time frame when account is taken of the time required by the request from the Arduino.
I don't understand this point. Either sending or receiving it is interrupt driven. A 64-byte buffer should be adequate for most purposes.
My idea is to be able to let the PC send X bytes in the background. That seems to work fine at the moment but obviously the PC can't send more than 64 bytes without the main code having to interrupt itself to go and empty the buffer. And if I can get the USART interrupt to write directly where I want the data then another step of processing can be eliminated.
The issue is that, by USB standards, sending 64 bytes is trivially short and the turnaround overhead between the Arduino asking for the next chunk of bytes and receiving them is a bottleneck. In much the same turnaround time I think the PC could send 80 or 100 bytes. At the moment the best I can do is get 11 bytes in about 4 msecs - but 44 bytes also takes 4 msecs. I suspect 88 won't take any longer.
Even after reading that thread I'm at a bit of a loss. First you can just increase the buffer size in HardwareSerial.cpp, that's not exactly rocket science:
// Define constants and variables for buffering incoming serial data. We're
// using a ring buffer (I think), in which head is the index of the location
// to which to write the next incoming character and tail is the index of the
// location from which to read.
#if (RAMEND < 1000)
#define SERIAL_BUFFER_SIZE 16
#else
#define SERIAL_BUFFER_SIZE 64
#endif
Second, I don't even see the point.
... without the main code having to interrupt itself ...
The main code already interrupts itself to put things into this buffer.
Making a second interrupt to copy things from one buffer to another doesn't achieve anything, except time-wasting as far as I can see. To say nothing of wasting valueable RAM.
Your* initial problem, at the start of this thread is this:
In the main loop, where you are already testing for Serial.available, simply copy bytes to your wanted buffer, and wait until a terminator arrives (eg. a newline). Then when it does (after multiple iterations through loop) process it. My examples show doing exactly that. The motor won't pause while you are doing this. You seem to think the problem is the small size of the hardware serial buffer. It isn't.