I'm currently attempting to replace delay() in my project so that i may perform other tasks while waiting on a particular event to fire.
The issue I'm having, is every example i've found using millis / elapsedmillis has everything inside the loop, where I'm trying to use it inside void buttons(). needless to say, either it's not going to work outside of loop, or I'm doing something ridiculous, and too tired, or too ignorant to see what i'm doing wrong..
The end goal is to have the button draw over the top of the existing one, wait 1000ms then draw the next button. (time interval will be changed later to sync with hardware spin down etc)
Here is the complete sketch, perhaps someone can show me a reliable way to replace delay in the buttons funciton:
#include <Adafruit_GFX.h>
#include <TouchScreen.h>
#include <stdint.h>
#include <SPI.h>
#include <SD.h>
#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;
#include <elapsedMillis.h>
elapsedMillis timer0;
elapsedMillis sinceTime1;
elapsedMillis sinceTime2;
elapsedMillis sinceTime3;
#define debug = false;
//#define debug = true;
#if defined(__SAM3X8E__)
#undef __FlashStringHelper::F(string_literal)
#define F(string_literal) string_literal
#endif
// most mcufriend shields use these pins and Portrait mode:
uint8_t YP = A1; // must be an analog pin, use "An" notation!
uint8_t XM = A2; // must be an analog pin, use "An" notation!
uint8_t YM = 6; // can be a digital pin
uint8_t XP = 7; // can be a digital pin
uint8_t Landscape = 0;
uint16_t TS_LEFT = 120;
uint16_t TS_RT = 940;
uint16_t TS_TOP = 940;
uint16_t TS_BOT = 180;
#define MINPRESSURE 10
#define MAXPRESSURE 1000
// For better pressure precision, we need to know the resistance
// between X+ and X- Use any multimeter to read it
// For the one we're using, its 250 ohms across the X plate
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
// Assign human-readable names to some common 16-bit color values:
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
int16_t BOXSIZE;
int16_t PENRADIUS = 3;
uint16_t oldcolor, currentcolor;
#define SD_SCK 13
#define SD_MISO 12
#define SD_MOSI 11
#define SD_CS 10
// In the SD card, place 24 bit color BMP files (be sure they are 24-bit!)
void setup() {
// Init TouchScreen
ts = TouchScreen(XP, YP, XM, YM, 300); //call the constructor AGAIN with new values.
BOXSIZE = tft.width() / 6;
tft.fillScreen(BLACK);
// put your setup code here, to run once:
Serial.begin(9600);
tft.reset();
uint16_t identifier = tft.readID();
if (identifier == 0x7789) {
//Serial.println(F("Found ST7789V LCD driver"));
TS_LEFT = 120; TS_RT = 940; TS_TOP = 940; TS_BOT = 180;
Landscape = 1;
} else {
//Serial.print(F("Unknown LCD driver chip: "));
//Serial.println(identifier, HEX);
return;
}
//Serial.print(F("Found LCD driver chip ID: "));
//Serial.println(identifier, HEX);
tft.begin(identifier);
//Serial.print(F("Initializing SD card..."));
if (!SD.begin(SD_CS, SD_MOSI, SD_MISO, SD_SCK)) {
Serial.println(F("failed!"));
return;
}
//Serial.println(F("OK!"));
tft.begin(identifier);
tft.setRotation(3);
BOXSIZE = tft.width() / 6;
tft.fillScreen(BLACK);
bmpDraw("splash.bmp", 80, 20);
delay(5000);
loadhome();
}
void loop() {
//DO STUFF HERE
//Testing Text stuff here
tft.setTextColor(BLACK);
tft.setTextSize(2);
tft.setCursor(15, 33);
tft.print("123.456");
tft.setTextColor(BLACK);
tft.setCursor(115, 33);
tft.print("543.210");
tft.setTextColor(BLACK);
tft.setCursor(215, 33);
tft.print("789.654");
buttons();
}
void loadhome(){
tft.setRotation(3);
BOXSIZE = tft.width() / 6;
tft.fillScreen(BLACK);
bmpDraw("cell.bmp", 10, 25);
bmpDraw("cell.bmp", 110, 25);
bmpDraw("cell.bmp", 210, 25);
bmpDraw("stop.bmp", 15, 175);
bmpDraw("locked.bmp", 90, 175);
bmpDraw("play.bmp", 165, 175);
bmpDraw("home.bmp", 240, 175);
bmpDraw("coolstp.bmp", 15, 105);
bmpDraw("spinstp.bmp", 240, 105);
tft.setTextColor(RED);
tft.setTextSize(3);
tft.setCursor(50, 3);
tft.print("X");
tft.setTextColor(RED);
tft.setCursor(150, 3);
tft.print("Y");
tft.setTextColor(RED);
tft.setCursor(250, 3);
tft.print("Z");
}
int homer = 0;
int play = 1;
int lock = 0;
int cool = 0;
int spin = 0;
const int interval = 1000;
unsigned long curMillis; //Current millis
void buttons(){
//Touchscreen stuff
TSPoint p = ts.getPoint();
// if sharing pins, you'll need to fix the directions of the touchscreen pins
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
p.x = map(p.x, TS_LEFT, TS_RT, 0, tft.width());
p.y = map(p.y, TS_TOP, TS_BOT, 0, tft.height());
/*
//Use to map buttons
//Be sure to mark out button code before use!!
if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
Serial.print(F("p.y ="));
Serial.print(p.y);
Serial.println("");
Serial.print(F("p.x ="));
Serial.print(p.x);
Serial.println("");
}
*/
// Begin Buttons
//Coolant Button
if (p.y > 110 && p.y < 170 && p.x > 250 && p.x < 300) { // if stop area is pressed
if(cool == 0){
bmpDraw("coolen.bmp", 15, 105);
//Serial.println(F("Coolant Button Pressed!"));
cool = 1;
} else {
bmpDraw("coolerr.bmp", 15, 105);
sinceTime1 = 0;
//delay(500); //replacing with millis()
if (sinceTime1 >= 1000) {
bmpDraw("coolstp.bmp", 15, 105);
//Serial.println(F("Coolant Button Pressed!"));
cool = 0;
}
}
}
//Stop Button
if (p.y > 185 && p.y < 235 && p.x > 250 && p.x < 300) { // if stop area is pressed
//Serial.println(F("Stop Button Pressed!"));
Serial.write(0x18);
Serial.print("\n");
}
//Lock Button
if (p.y > 185 && p.y < 235 && p.x > 182 && p.x < 234) { // if stop area is pressed
if(lock == 0){
Serial.print("$X\n");
bmpDraw("unlocked.bmp", 90, 175);
//Serial.println(F("Lock/Unlock Button Pressed!"));
lock = 1;
} else {
Serial.print("$X\n");
bmpDraw("locked.bmp", 90, 175);
//Serial.println(F("Lock/Unlock Button Pressed!"));
lock = 0;
}
}
//Play-Pause Button
if (p.y > 185 && p.y < 235 && p.x > 110 && p.x < 160) { // if stop area is pressed
if(play == 0){
Serial.print("~\n");
bmpDraw("play.bmp", 165, 175);
//Serial.println(F("Play/Pause Button Pressed!"));
play = 1;
} else {
Serial.print("!\n");
bmpDraw("pause.bmp", 165, 175);
//Serial.println(F("Play/Pause Button Pressed!"));
play = 0;
}
}
//Home Button
if (p.y > 185 && p.y < 235 && p.x > 35 && p.x < 90) { // if stop area is pressed
if(homer == 0){
bmpDraw("homeg.bmp", 240, 175);
//Serial.println(F("Home Button Pressed!"));
homer = 1;
} else {
Serial.print("$H\n");
bmpDraw("homey.bmp", 240, 175);
sinceTime2 = 0;
//delay(500); //replacing with millis()
if (sinceTime2 >= 1000) {
bmpDraw("homer.bmp", 240, 175);
}
//delay(500); //replacing with millis()
sinceTime2 = 0;
if (sinceTime2 >= 1000) {
//delay(750);
bmpDraw("homeg.bmp", 240, 175);
//Serial.println(F("Home Button Pressed!"));
}
homer = 1;
}
}
//Spindle Button
if (p.y > 110 && p.y < 170 && p.x > 35 && p.x < 90) { // if stop area is pressed
if(spin == 0){
bmpDraw("spinen.bmp", 240, 105);
//Serial.println(F("Spindle Button Pressed!"));
spin = 1;
} else {
bmpDraw("spinerr.bmp", 240, 105);
sinceTime3 = 0;
//delay(500); //replacing with millis()
if (sinceTime3 >= 1000) {
bmpDraw("spinstp.bmp", 240, 105);
//Serial.println(F("Spindle Button Pressed!"));
}
spin = 0;
}
}
}
#define BUFFPIXEL 15
void bmpDraw(char *filename, int x, int y) {
File bmpFile;
int bmpWidth, bmpHeight; // W+H in pixels
uint8_t bmpDepth; // Bit depth (currently must be 24)
uint32_t bmpImageoffset; // Start of image data in file
uint32_t rowSize; // Not always = bmpWidth; may have padding
uint8_t sdbuffer[3*BUFFPIXEL]; // pixel in buffer (R+G+B per pixel)
uint16_t lcdbuffer[BUFFPIXEL]; // pixel out buffer (16-bit per pixel)
uint8_t buffidx = sizeof(sdbuffer); // Current position in sdbuffer
boolean goodBmp = false; // Set to true on valid header parse
boolean flip = true; // BMP is stored bottom-to-top
int w, h, row, col;
uint8_t r, g, b;
uint32_t pos = 0, startTime = millis();
uint8_t lcdidx = 0;
boolean first = true;
if((x >= tft.width()) || (y >= tft.height())) return;
//Serial.println();
//Serial.print(F("Loading image '"));
//Serial.print(filename);
//Serial.println('\'');
// Open requested file on SD card
if ((bmpFile = SD.open(filename)) == NULL) {
Serial.println(F("File not found"));
return;
}
// Parse BMP header
if(read16(bmpFile) == 0x4D42) { // BMP signature
read32(bmpFile);
//Serial.println(F("File size: "));
//Serial.println(read32(bmpFile));
(void)read32(bmpFile); // Read & ignore creator bytes
bmpImageoffset = read32(bmpFile); // Start of image data
//Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
// Read DIB header
read32(bmpFile);
//Serial.print(F("Header size: "));
//Serial.println(read32(bmpFile));
bmpWidth = read32(bmpFile);
bmpHeight = read32(bmpFile);
if(read16(bmpFile) == 1) { // # planes -- must be '1'
bmpDepth = read16(bmpFile); // bits per pixel
//Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
goodBmp = true; // Supported BMP format -- proceed!
// Serial.print(F("Image size: "));
// Serial.print(bmpWidth);
// Serial.print('x');
// Serial.println(bmpHeight);
// BMP rows are padded (if needed) to 4-byte boundary
rowSize = (bmpWidth * 3 + 3) & ~3;
// If bmpHeight is negative, image is in top-down order.
// This is not canon but has been observed in the wild.
if(bmpHeight < 0) {
bmpHeight = -bmpHeight;
flip = false;
}
// Crop area to be loaded
w = bmpWidth;
h = bmpHeight;
if((x+w-1) >= tft.width()) w = tft.width() - x;
if((y+h-1) >= tft.height()) h = tft.height() - y;
// Set TFT address window to clipped image bounds
tft.setAddrWindow(x, y, x+w-1, y+h-1);
for (row=0; row<h; row++) { // For each scanline...
// Seek to start of scan line. It might seem labor-
// intensive to be doing this on every line, but this
// method covers a lot of gritty details like cropping
// and scanline padding. Also, the seek only takes
// place if the file position actually needs to change
// (avoids a lot of cluster math in SD library).
if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
else // Bitmap is stored top-to-bottom
pos = bmpImageoffset + row * rowSize;
if(bmpFile.position() != pos) { // Need seek?
bmpFile.seek(pos);
buffidx = sizeof(sdbuffer); // Force buffer reload
}
for (col=0; col<w; col++) { // For each column...
// Time to read more pixel data?
if (buffidx >= sizeof(sdbuffer)) { // Indeed
// Push LCD buffer to the display first
if(lcdidx > 0) {
tft.pushColors(lcdbuffer, lcdidx, first);
lcdidx = 0;
first = false;
}
bmpFile.read(sdbuffer, sizeof(sdbuffer));
buffidx = 0; // Set index to beginning
}
// Convert pixel from BMP to TFT format
b = sdbuffer[buffidx++];
g = sdbuffer[buffidx++];
r = sdbuffer[buffidx++];
lcdbuffer[lcdidx++] = tft.color565(r,g,b);
} // end pixel
} // end scanline
// Write any remaining data to LCD
if(lcdidx > 0) {
tft.pushColors(lcdbuffer, lcdidx, first);
}
// Serial.print(F("Loaded in "));
//Serial.print(millis() - startTime);
//Serial.println(" ms");
} // end goodBmp
}
}
bmpFile.close();
if(!goodBmp) Serial.println(F("BMP format not recognized."));
}
// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.
uint16_t read16(File f) {
uint16_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read(); // MSB
return result;
}
uint32_t read32(File f) {
uint32_t result;
((uint8_t *)&result)[0] = f.read(); // LSB
((uint8_t *)&result)[1] = f.read();
((uint8_t *)&result)[2] = f.read();
((uint8_t *)&result)[3] = f.read(); // MSB
return result;
}
-
5Please edit your code down to the minimum required to demonstrate and debug the problem.Transistor– Transistor2016年01月13日 18:36:00 +00:00Commented Jan 13, 2016 at 18:36
-
Do you want the buttons and processes to act independently from each other, or do you want some kind of blocking? Like "Press Coolant" runs does the coolant for 1000ms, but does or does not allow "Stop" to be pressed? do the buttons block each other?Dave X– Dave X2016年01月13日 21:34:24 +00:00Commented Jan 13, 2016 at 21:34
-
For "Spindle Button" should what should happen if you hold your finger on it for 10 seconds? Start up spin, make the button be 'spinen.bmp' for one loop(), then display 'spinerr.bmp' for 1000ms, then display 'spinstp.bmp', shut off the spin, and repeat? I'd keep a state variable for the screen handling a touch so you don't cycle/race, and add it to the if(p...) condition. I think I'd move the button-de-animation out of the if(p....) condifional.Dave X– Dave X2016年01月13日 22:04:09 +00:00Commented Jan 13, 2016 at 22:04
-
The whole idea is to give a visual representation of the device doing "something". My thinking was to time how long it takes the spindle to spin down and stop, use that time for the timer.. So an example would be, the spinde takes 5 seconds to slow down and stop, when user push the button, the command to stop the spindle is sent, the button is redrawn from green(on) to yellow(slowing) then to red(stopped) after 5secs. same for coolant, and homing. I'm trying to use something other than delay, as i need serial to update the X, Y and Z positions. and have access to stop in case of emergencyArcAiN6– ArcAiN62016年01月14日 00:08:05 +00:00Commented Jan 14, 2016 at 0:08
2 Answers 2
The usual technique at this scale of technology is to keep loop() pretty short, mostly making frequent calls to a timer function that keeps a list of things to be done (implemented with callback-functions) and the times at which to do them, and which calls those functions at the appropriate times. The upper level(s) of your sketch would schedule tasks by registering one or more callback-functions to be run at a given time or periodically.
When the timer function is called, if there is nothing to do, or when a callback-function returns, the timer function returns immediately to its caller (probably loop()). loop() can do other work, as long as it can still call the timer function as frequently as your task dispatching requires.
The SimpleTimer library at Arduino Playground is an implementation of such a timer-function.
Taking JRobert's advice, i checked out the simpletimer library.. the final solution was to set up three timers and change the way the buttons functioned.
as per documentation of the simpletimer library, added the following global:
#include <SimpleTimer.h>
SimpleTimer timer;
...
Then to setup, i added my three timers
timer.setInterval(7000, spindle); //times based on hardware reality
timer.setInterval(2000, coolant);
timer.setInterval(21500, homer);
...
Then started the timers in loop by adding:
timer.run();
...
Finally, i edited the buttons and added variables to denote state of button. i.e.
/*
0 == on,
1 == transitional1
2 == transitional2
3 == stopped
*/
int homeb = 0;
int cool = 0;
int spin = 0;
Changed the touch screen code for each button to kick off the timers...
//Coolant Button
if (p.y > 110 && p.y < 170 && p.x > 250 && p.x < 300) { // if stop area is pressed
if(cool == 3){
bmpDraw("coolen.bmp", 15, 105);
//PWM the spindle pin on
cool = 0;
} else {
if(cool == 0){
//PWM the spindle pin off
cool = 1; //Start the timer sequence for stopping coolant
}
}
}
then i added a void for each timer, this is where i redraw the buttons
void coolant() {
if (cool == 1){
bmpDraw("coolerr.bmp", 15, 105);
cool = 2;
} else {
if (cool = 2){
bmpDraw("coolstp.bmp", 15, 105);
cool = 3;
}
}
}
Hope this helps someone else in the future, as it was a real pickle for me to figure out at first.. Thanks again, and credit goes to JRobert, his advice pointed me directly to the solution.
-
Glad to have helped - and thanks for posting your implementation.JRobert– JRobert2016年01月14日 19:34:29 +00:00Commented Jan 14, 2016 at 19:34