C++/C Arduino Stopwatch code check - arduino

I'm making a stopwatch with an arduino with a vibration motor and a simple button as the interface.
I'm coming to a hold with this project as it just won't work, i've been testing it on my uno with no success so far, I was wondering if someone could give me a quick little run-down and see if they can spot any big issues that i've overlooked.
No errors in code, I think it might be a logical error on my behalf or possibly even an error with my board, but I doubt that!
void setup() {
Serial.begin(9600);
}
int lengthOf(int i)
{
if (i < 0){i = -i;}
if (i < 10){ return 1;}
if (i < 100){ return 2;}
if (i < 1000){ return 3;}
if (i < 10000){ return 4;}
if (i < 100000){ return 5;}
if (i < 1000000){ return 6;}
if (i < 10000000){ return 7;}
if (i < 100000000){ return 8;}
if (i < 1000000000){ return 9;}
return 10;
}
void loop() {
int ButtonSwitch = 4;
pinMode(4, INPUT);
int motor = 5;
int timerA = 0; int timed;
bool checker = false; //checker acts to see if the current state is timing/counting
bool shown; //Shown acts as a check to show if the time has already been shown
if (analogRead(ButtonSwitch) == HIGH && checker == false)//When the button is pressed and the state is false then
{
checker = true;//sets checker to true, meaning the timing should begin
shown = false;//sets the shown variable to false so as to
timerA = 0;//reset timer to a 0 value
}
while (checker == true)//while the timer is active then do the following
{
timerA++;//Increment the timer
if (digitalRead(ButtonSwitch) == HIGH)
{
checker = false;
break;
}
delay(1000);//No needfor the simpleTimer library as I don't need to run any code inbetween each second
}
//Sets the timed to the real value of
timed = lengthOf(timerA);//Grabs the length of timerA (1223 would be 4)
int recTime[timed - 1]; //creates an array of the same length as the timer
//append int to chars and by extent an array
char str[timed];
sprintf(str, "%d", timerA);
int numbers;
for (int i = 0; i < timed; i++)
{
recTime[i] = str[i] - '0';//This grabs STR which is an empty array of the length of the time then sets recTime to be the same
}
while ((analogRead(ButtonSwitch) == LOW) && (checker == false) && (shown == false))//Loop checks that the button is not pressed, the checker is false, and that the time has not been shown,
{
for (int i = 0; i < timed; i++)//
{
for (int o = 0; o < recTime[i]; o++)
{
digitalWrite(motor, HIGH);//Motor set to vibrate
delay(500);//1/2 second delay
digitalWrite(motor, LOW);//motor off
delay(300);//3/10 second delay
}
delay(3000);//3 second delay
}
shown = true;
}
}
If there's any more info I can provide then let me know,
any help is greatly appreciated!

I've read through your code and have a few suggestions. First, let's be consistent and use a digitalRead instead of the analogRead near the top and bottom of the loop routine. Let's move all of your variable declarations before the setup routine. (you will still need to initialize timerA, checker and shown at the top of loop) Also, you only need to set the pinMode for the button once, so we'll move that line to the setup routine and, while we're at it, let's use the variable ButtonSwitch in the pinMode command. e.g.
int ButtonSwitch = 4;
int motor = 5;
int timerA = 0;
int timed;
bool checker = false;
bool shown;
void setup() {
Serial.begin(9600);
pinMode(ButtonSwitch, INPUT);
}
But I think the real problem could be with the logical flow of the program. Assume the loop starts, checker is false and the button is not pressed. When you press the button, checker gets set to true. Immediately after that, within a few clocks ticks, the next block is entered (because checker is true) and it sets checker to false and exits the block if the button is still being pressed. The chances of you releasing the button between the first block and the second is pretty much zero. So your program just keeps setting timerA to 0, then incrementing it by 1 and moving on. The delay(1000) is never reached and timerA never gets above 1, the recTime array will always be defined as recTime[0] and the str array will always be defined as str[1].
In the second block, the while loop, I think you want to change
if (digitalRead(ButtonSwitch) == HIGH) to if (digitalRead(ButtonSwitch) == LOW).
I think if you rectify these issues, you'll be a long way towards getting git working!

In C, when timed is the number of chars needed to write the decimal digits of a number, then you should allocate one more for the terminating '\0'.
char str[timed+1];
Then, I'm wondering what you're doing with the recTime values. Indeed, whenever for a value of 123, str would contain "123", i.e. ['1','2','3'], then retTime would contain [1,2,3].

Related

Arduino readString(); code runs slow

I have the following code which I need to execute quickly, yet its taking a lot of time to change the value, anyway over way of making this task quicker?
I am using indexOf() and substring() to accomplish this task.
This is for changing the strip LED colors.
// declare LED Series A Pins R-G-B (PWM Pins)
int const AledRedPin = 6;
int const AledGreenPin = 5;
int const AledBluePin = 3;
// declare LED Series B Pins R-G-B (PWM Pins)
int const BledRedPin = 10;
int const BledGreenPin = 11;
int const BledBluePin = 9;
// serial input variable & string
// initialise LED Series A Pins R-G-B (PWN Value: 0 to 255)
// initial value = 255
int AledRed = 255;
int AledGreen = 255;
int AledBlue = 255;
// initialise LED Series A Pins R-G-B (PWN Value: 0 to 255)
// initial value = 255
int BledRed = 255;
int BledGreen = 255;
int BledBlue = 255;
//serial input
String Command = "";
//string manipulation
int cmdindexval = 0;
String CommandType = "";
int CommandValue = 0;
String Series = "";
void setup() {
// put your setup code here, to run once:
// start serial
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB
}
// set LED Series A Pins as Output R-G-B
pinMode(AledRedPin, OUTPUT);
pinMode(AledGreenPin, OUTPUT);
pinMode(AledBluePin, OUTPUT);
// set LED Series B Pins as Output R-G-B
pinMode(BledRedPin, OUTPUT);
pinMode(BledGreenPin, OUTPUT);
pinMode(BledBluePin, OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
// read from serial if it's available
if (Serial.available() > 0) {
Command = Serial.readString(); //read string from serial monitor
cmdindexval = Command.indexOf('='); //read characters until '=' then assign the value
CommandType = Command.substring(0, cmdindexval); //assign the value from 0 to cmdindexval
//Series = Command.substring(0, 1); //read first character
CommandValue = Command.substring(cmdindexval + 1).toInt(); //assign the value after '=' and convert string to Int
Serial.println(CommandType + " ,is equal to " + CommandValue + " ,Series: " + Series);
//if (Series == "A") {
if (CommandType == "ACledRed"){
AledRed = CommandValue;
}
else if (CommandType == "ACledGreen"){
AledGreen = CommandValue;
}
else if (CommandType == "ACledRedBlue") {
AledBlue = CommandValue;
}
//}
//else if (Series == "B") {
if (CommandType == "BCledRed") {
BledRed = CommandValue;
}
else if (CommandType == "BCledGreen") {
BledGreen = CommandValue;
}
else if (CommandType == "BCledBlue") {
BledBlue = CommandValue;
}
//}
} //end serial
analogWrite(AledRedPin, AledRed);
analogWrite(AledGreenPin, AledGreen);
analogWrite(AledBluePin, AledBlue);
analogWrite(BledRedPin, BledRed);
analogWrite(BledGreenPin, BledGreen);
analogWrite(BledBluePin, BledBlue);
}
From the Arduino docs on readString:
Serial.readString() reads characters from the serial buffer into a string. The function terminates if it times out (see setTimeout()).
and the docs on setTimeout:
Serial.setTimeout() sets the maximum milliseconds to wait for serial data when using Serial.readBytesUntil(), Serial.readBytes(), Serial.parseInt() or Serial.parseFloat(). It defaults to 1000 milliseconds.
This means that the readString is always waiting 1 sec to make sure that the sending of the string is finished and has the complete string.
Unfortunately that means it's slow to respond. You could lower the timeout with the setTimeout, but you would still have some delay, or if you set it too low you could potentially get incomplete stings.
The best solution would be to use readStringUntil, so you know you have a complete string when you get a terminator character (like a newline).
Replace
Command = Serial.readString();
with
Command = Serial.readStringUntil('\n');
and make sure you set the Serial monitor so send the newline character.
Edit: see important update at the end.
This can be made significantly faster, but first let's have a look at the work that has to be done in every loop iteration with the current code:
As #gre_gor already explained, you could be losing some time in readString().
for each value, between 15 and 20 bytes have to be sent, read, parsed and converted to int.
for each received value (R, G or B), analogWrite() is called 6 times (and analogWrite() isn't really fast). This means that in order to change the two series, analogWrite() is called 36 times (and this is probably where most time is lost). And if no serial data is available, analogWrite() is still called 6 times.
and also, Serial.println() is called each time in the example - so it would be best to turn this off.
To speed this up, the RGB values could be sent in a small buffer (assuming you have control over the sending side as well), and read with Serial.readBytesUntil().
If the values for both A and B are sent together, the 6 RGB values can be sent as 6 bytes:
byte rcvBuffer[7];
void loop() {
if (Serial.available() > 0) {
// message: RGBRGBx - but see update below
int numRead = Serial.readBytesUntil(0x78, rcvBuffer, 7); // 0x78 is 'x'
if (numRead == 7) { // or 6, see below
analogWrite(AledRedPin, rcvBuffer[0]);
analogWrite(AledGreenPin, rcvBuffer[1]);
analogWrite(AledBluePin, rcvBuffer[2]);
analogWrite(BledRedPin, rcvBuffer[3]);
analogWrite(BledGreenPin, rcvBuffer[4]);
analogWrite(BledBluePin, rcvBuffer[5]);
}
// else ignore this read - could be a first unaligned read
}
}
If only the values for A or B are sent together:
byte rcvBuffer[5];
void loop() {
// You could probably even remove the Serial.available() check
if (Serial.available() > 0) {
// message: TRGBx where T is Type ('A' or 'B')
int numRead = Serial.readBytesUntil(0x78, rcvBuffer, 5); // 0x78 is 'x'
if (numRead == 5) { // or 4, see below
switch (rcvBuffer[0]) {
case 'A':
analogWrite(AledRedPin, rcvBuffer[1]);
analogWrite(AledGreenPin, rcvBuffer[2]);
analogWrite(AledBluePin, rcvBuffer[3]);
break;
case 'B':
analogWrite(BledRedPin, rcvBuffer[1]);
analogWrite(BledGreenPin, rcvBuffer[2]);
analogWrite(BledBluePin, rcvBuffer[3]);
break;
default :
// do nothing, or send error message
}
}
}
}
I used 'x' as the stop byte to make it visible, but you could as well use a zero byte.
Now, I'm not really sure if readBytesUntil() also reads the terminating byte into the buffer or skips it, and can't test this right now. But I would think only the RGB values are read into the buffer. In this case you'll have to change those values to the ones I put in the comments.
To save even more time, you could check each value and only call analogWrite() if that value did change since the last call (for each R, G and B).
Update: Obviously we can't just use 'x' or a zero byte as the stop byte, because each of the RGB values could also be an 'x' or zero byte (it's getting late here :). And while ReadBytes() could be used instead, it's better to have a stop byte to keep the buffers aligned. So I would suggest to use 0xff (255) as the stop byte and make sure none of the RGB values can be 0xff.
And just in case there could be other message types in the future, each message could also be prepended with a message code (1 or 2 bytes).
I always use readBytesUntil()whenever I use the serial port for communication.
It gets the job done, it always gets the entire string, but the same problem as readString()takes at least 1000ms to complete.
Using both Serial.setTimeout() and Serial.readBytesUntil() worked fine for me, by reducing the delay.
Something like:
Serial.setTimeout(250);
inData = Serial.readStringUntil('\n');

create a timed 3 state push button in arduino

Due to a shortage of pins on a esp8266 in arduino, I need a way to detect a button where;
momentary press runs snooze()
15 sec press runs conf_Desk()
30 sec press runs calibration()
the preconfig;
int buttonPin = D7;
pinMode( buttonPin , INPUT_PULLUP);
All while allowing the main loop to function.
If I trap an interrupt it stops cycling the loop(), a few millisec delays are OK but seconds of delay is too much.
The functions are already written I just can't seem to come up how to track and confirm the hold length to call the right function based on the right timing without stopping other process that must stay cycling.
Using an interrupt is, IMHO, overkill. Interrupts are made for when you need to reply to a stimulus quickly, and a button press is something slow. Unless your loop is blocking, thing that I highly discourage.
ADDITION: as Patrick pointed out in the comments, there is in fact another reason to use interrupts: sleep mode. In fact, if you want to go into sleep mode and wake with a button, you have to use interrupts to wake later. However usually you have to do something continuously and not only reply to the button inputs. If you can't go into sleep mode, using an interrupt for button detection is still overkill in my opinion.
So, if you properly designed your loop not to block, here is a brief part of code doing what I think you should implement:
uint8_t buttonState;
unsigned long lastPressTime;
void setup()
{
...
buttonState = digitalRead(buttonPin);
lastPressTime = 0;
}
void loop()
{
uint8_t currRead = digitalRead(buttonPin);
if (buttonState != currRead)
{ // Button transition
buttonState = currRead;
if (buttonState == LOW)
{ // Button pressed, start tracking
lastPressTime = millis();
}
else
{ // Button released, check which function to launch
if (lastPressTime < 100)
{} // Discard (it is just a bounce)
else if (lastPressTime < 15000)
snooze();
else if (lastPressTime < 30000)
conf_Desk();
else
calibration();
}
}
...
}
Since you made three very distant intervals though, I think that this part better suits your needs:
if ((lastPressTime > 100) && (lastPressTime < 7000))
snooze();
else if ((lastPressTime > 12000) && (lastPressTime < 20000))
conf_Desk();
else if ((lastPressTime > 26000) && (lastPressTime < 40000))
calibration();
So you define validity ranges, so if someone presses the button for 10 seconds nothing happens (this is useful because if someone presses the button for 14.9 seconds in the previous code it will trigger the snooze function).
i would use a simple state machine structure with two global vars to avoid complex nested logic:
int buttonDown = 0;
unsigned long buttonStart;
void loop(){
int snapshot = digitalRead(buttonPin);
if(!buttonDown && snapshot ){ //pressed, reset time
buttonDown = 1; // no longer unpressed
buttonStart = millis(); // when it was pressed
}
if(buttonDown && !snapshot ){ //released, count time
buttonDown = 0; // no longer pressed
int duration = millis() - buttonStart; // how long since pressed?
// now the "event part"
if(duration>30000) return calibration();
if(duration>15000) return conf_Desk();
snooze();
}
sleep(1); // or whatever
}
Interrupt service routines should be a short as possible. You don't have to wait inside the ISR and suspend your main loop for seconds.
Just use two different ISRs for a rising and a falling edge.
When the button is pressed, ISR1 starts a timer, when it is released ISR2 stop it and triggers whatever is necessary depending on the time passed.
Make sure your button is debounced.
https://www.arduino.cc/en/Reference/attachInterrupt
Another way to do this is with a pointer-to-function based state machine.
The advantage on this is that you can easily introduce more functionalities to your button (say, another function called at 45 seconds).
try this:
typedef void(*state)();
#define pressed (millis() - lastPressed)
void waitPress();
void momentPress();
void shortPress();
void longPress();
state State = waitPress;
unsigned long lastPressed;
int buttonState;
int buttonPin = 7;// or whathever pin you use
void snooze(){} // stubs for your functions
void conf_Desk(){}
void callibration(){}
void waitPress()
{
if (buttonState == HIGH)
{
lastPressed = millis();
State = momentPress;
return;
}
else
return;
}
void momentPress()
{
if (buttonState == LOW)
{
snooze();
State = waitPress;
return;
}
if (pressed > 15000)
State = shortPress;
return;
return;
}
void shortPress()
{
if (buttonState == LOW)
{
conf_Desk();
return;
}
if (pressed > 30000)
State = longPress;
return;
return;
}
void longPress()
{
if (buttonState == LOW)
{
callibration();
return;
}
return;
}
void loop()
{
buttonState = digitalRead(buttonPin);
State();
}

Arduino not entering for loop

I'm having a problem with an if ladder and the for loops in each of them in this gameProcess function. Essentially the for loop in the (mode == 1) loop is not entered at all and I'm not sure why.
I think it's something to do with the positioning because if (mode == 1) is switched with (mode == 0), the (mode == 1) for loop will be entered but the (mode == 0) won't. Been stuck on this for a while and can't seem to spot what's up with the function. Any help would be greatly appreciated. Thanks.
// 5 means 10 switches as one iteration is a change between one mode to another, not on and off.
int gameProcess(int mode) {
Serial.println("> Starting game process.");
// 4 second delay between
int interval = 1000;
int initialDelay = 5000;
enter code here:
//delay(initialDelay);
if (mode == 1) {
// for a standard 25 round with a 2 sec interval
Serial.println("starting mode 1");
for (int i; i < 51; i++) {
Serial.println("In loop");
Serial.println(interval);
flickPin(13);
workingDelay(interval);
}
Serial.println("finished mode 1");
} else if (mode == 2) {
while(true) {
flickPin(13);
int minRandVal = 1000;
int maxRandVal = 15000;
int randomDelay = random(minRandVal, maxRandVal);
Serial.println(randomDelay);
workingDelay(randomDelay);
}
} else if (mode == 0) {
// for 10 rounds. Find out why it needs 35 and not 20.
Serial.println(" - Mode 0 .");
for (int i; i < 35; i++) {
Serial.println(interval);
flickPin(13);
workingDelay(interval);
}
Serial.println(" - finished Mode 0 .");
} else {
char error[80];
sprintf(error, "Unknown mode = %d", mode);
Serial.println(error);
}
}
Here is the main loop and the initializeGame function where the gameProcess function is called from.
int initializeGame(bool started, int mode) {
if (started == true) {
Serial.println(" -> in startMonitor, start button pressed");
Serial.println(mode);
gameProcess(mode);
// if the mode is 0 (none of the lights are on), that means it's in random mode and any interval between 5 and 60 secs come up until you stop it!
// set it back to false and turn the game light off because the game is over.
started = false;
digitalWrite(9, LOW);
} else {
started = false;
}
return started;
}
void loop()
{
// check if the pushbutton is pressed.
mode = stateMonitor(activeButton);
// now set the game mode led.
lightUpModeLed(ledPins[mode]);
// now check to see if the game has been initialized.
started = startMonitor(startButton, ledPins[4], started);
// read the state of the pushbutton value:
// initialize game if it hasn't already started.
// TODO this is where the loop will likely spend the majority of it's time so how can this be
started = initializeGame(started, mode);
saveData();
}
if you are using windows then install Atmel Studio and the Visual Micro Plugin then you can debug and watch what the code is doing and the values of variables.
This will take away the mystery, which it did for me.

Last chunk of data truncated on arduino serial data

I have an arduino taking serial input and will turn on the leds. The code is below.
I have a strange problem that when I send multiples of 120x bytes e.g., 240, 480 the last 120 bytes never get read completely.
I see on the serial monitor 120 120 120 81 if I send 480 bytes of data. Could anyone point out the mistake?
#include "FastLED.h"
#define DATA_PIN 6
#define NUM_LEDS 40
byte colors[120];
CRGB leds[NUM_LEDS];
void setup(){
FastLED.addLeds<NEOPIXEL, DATA_PIN, RGB>(leds, NUM_LEDS);
Serial.begin(115200);
}
void loop(){
if (Serial.available()){
int i =0;
char incomingByte;
while(1) {
incomingByte = Serial.readBytes((char *)colors,120);
break;
}
Serial.print(incomingByte);
for(i=0;i<NUM_LEDS ;i++){
leds[i].green = colors[i];
leds[i].red = colors[i+1];
leds[i].blue = colors[i+2];
}
if(incomingByte==0x78){
FastLED.show();
}
}
}
your code is flawed in different ways.
First, please remove the useless use of while(1) {…; break;}, it's just adding an overhead and adds nothing to your algorithm.
Otherwise, your code is not working well because, I guess, at some point there's a lag happening in the serial communication that causes the read to timeout. Let's have a look at source code.
First, you take the readBytes() function. All it does is:
size_t Stream::readBytes(char *buffer, size_t length)
{
size_t count = 0;
while (count < length) {
int c = timedRead();
if (c < 0) break;
*buffer++ = (char)c;
count++;
}
return count;
}
i.e. it iterates length times over the blocking read function. But it breaks if that function's return value is less than zero, returning less than length bytes. So that what's happening to get less than length, so let's have a look at timedRead():
int Stream::timedRead()
{
int c;
_startMillis = millis();
do {
c = read();
if (c >= 0) return c;
} while(millis() - _startMillis < _timeout);
return -1; // -1 indicates timeout
}
what happens here, is that if the read succeeds, it returns the read value, otherwise it loops until timeout has passed, and returns -1, which will end readBytes immediately. The default value for the timeout is 1000ms, though you can make that value higher by using Serial.setTimeout(5000); in your setup() function.
Though you have nothing to earn by using the blocking readBytes() function. So you'd better instead write your loop so you read the values, and trigger an event only once all values have been read:
#define NB_COLORS 120
void loop() {
static byte colors[NB_COLORS];
static int colors_index=0;
if (colors_index < NB_COLORS) {
// let's read only one byte
colors[colors_index] = Serial.read();
// if a byte has been read, increment the index
if (colors[colors_index] != -1)
++colors_index;
} else {
// reset the index to start over
colors_index = 0;
// should'nt you iterate 3 by 3, i.e. having i=i+3 instead of i++ ?
for(i=0;i<NUM_LEDS ;i++){
leds[i].green = colors[i];
leds[i].red = colors[i+1];
leds[i].blue = colors[i+2];
}
FastLED.show();
}
}
HTH

Having trouble getting my stepper motor to respond to my button sensor in Arduino sketch

I'm using the AccelStepper library to control my stepper motor, and I'm having difficulty figuring out how to get my motor to stop when my button is pushed.
I can get the motor to stop once it completes its entire movement from the moveTo command, but I can't get it to stop before it finishes. I've tried using an if statement nested within the while loop I'm using to get the motor to run, but no dice.
The code as it stands is as follows:
#include <AccelStepper.h>
const int buttonPin=4; //number of the pushbutton pin
int buttonState=0;
int motorSpeed = 9600; //maximum steps per second (about 3rps / at 16 microsteps)
int motorAccel = 80000; //steps/second/second to accelerate
int motorDirPin = 8; //digital pin 8
int motorStepPin = 9; //digital pin 9
int state = 0;
int currentPosition=0;
//set up the accelStepper intance
//the "1" tells it we are using a driver
AccelStepper stepper(1, motorStepPin, motorDirPin);
void setup(){
pinMode(buttonPin,INPUT);
stepper.setMaxSpeed(motorSpeed);
stepper.setSpeed(motorSpeed);
stepper.setAcceleration(motorAccel);
stepper.moveTo(-12000); //move 2000 steps (gets close to the top)
}
void loop(){
while( stepper.currentPosition()!=-10000)
stepper.run();
// move to next state
buttonState = digitalRead(buttonPin);
currentPosition=stepper.currentPosition();
// check if the pushbutton is pressed.
// if it is, the buttonState is HIGH:
//if stepper is at desired location
if (buttonState == HIGH ){//need to find a way to alter current move to command
stepper.stop();
stepper.runToPosition();
stepper.moveTo(12000);
}
if(stepper.distanceToGo() == 0)
state=1;
if(state==1){
stepper.stop();
stepper.runToPosition();
stepper.moveTo(12000);
}
//these must be called as often as possible to ensure smooth operation
//any delay will cause jerky motion
stepper.run();
}
I don't know if you've figured it out already, but I stumbled upon this thread and noticed something which might or might not solve your problem. I'm working with accelstepper too at the moment.
I'm having the feeling that even though you use .stop to stop the motor, you're still assigning a new destination (stepper.moveTo(12000)), after which you still run the stepper.run() command at the bottom, causing the stepper to 'run towards 12000 steps'. Maybe try this?
uint8_t button_state = 0; // cf problem 1.
void loop() {
if (digitalRead(buttonPin) == HIGH) {
if (button_state == 0) {
stepper.stop();
stepper.moveTo(12000);
button_state = 1;
}
} else {
button_state = 0;
}
if (stepper.distanceToGo() == 0) {
stepper.stop();
stepper.moveTo(12000);
}
if(button_state = 0) {
stepper.run();
}
}
I don't know if this is going to work, but this way, if the button is pressed, the button_state variable should prevent the stepper from running when it's set on 1.
Hope this helps,
Just a passing by student
I see three problems in your code:
when you push the button, its state will be set to HIGH for the whole time you're pressing it, and it can be several loops. You'd better use a state variable that triggers what you want to do on button press only once.
looking at the documentation, you're using stepper.runToPosition(), which blocks until it reaches the destination. So the more you click, the more it could get blocked. You'd better use only the stepper.moveTo() and stepper.run() method that enables to use a few cycles for interactions.
at the beginning of your loop, you make your code block until stepper.currentPosition gets to -10000. So you surely are blocking until it gets there, removing all reactivity on every loop() iteration.
you may better work your loop() function out as follows:
uint8_t button_state = 0; // cf problem 1.
void loop() {
if (digitalRead(buttonPin) == HIGH) {
if (button_state == 0) {
stepper.stop();
stepper.moveTo(12000);
button_state = 1;
}
} else {
button_state = 0;
}
if (stepper.distanceToGo() == 0) {
stepper.stop();
stepper.moveTo(12000);
}
stepper.run();
}

Resources